1.优化充值跳转链接的问题
2.优化后台渠道管理页面的显示样式
This commit is contained in:
@@ -9,7 +9,9 @@ export default {
|
||||
admin_username: 'Agent username',
|
||||
commission_rate: 'Commission rate',
|
||||
calc_base_amount: 'Calculation base amount',
|
||||
commission_amount: 'Commission amount',
|
||||
commission_amount: 'Commission amount (gross)',
|
||||
handling_fee: 'Handling fee amount',
|
||||
net_commission_amount: 'Net commission',
|
||||
status: 'Status',
|
||||
'status 0': 'Pending',
|
||||
'status 1': 'Paid',
|
||||
|
||||
@@ -15,6 +15,19 @@ export default {
|
||||
agent_mode_desc_affiliate_2: 'Formula: net loss after costs × affiliate share rate.',
|
||||
agent_mode_desc_affiliate_3: 'Fields: supports fee deduction rate and carryover balance; turnover rate is cleared automatically.',
|
||||
turnover_share_rate: 'turnover_share_rate',
|
||||
settlement_handling_fee: 'settlement_handling_fee(%)',
|
||||
settlement_handling_fee_tip: 'Percent of gross commission per agent per period; adjustable in manual settle',
|
||||
settlement_handling_fee_percent: 'handling_fee(%)',
|
||||
settlement_handling_fee_amount: 'handling_fee_amount',
|
||||
manual_settle_settlement_base: 'settlement_base',
|
||||
manual_settle_net_commission: 'net_commission',
|
||||
manual_settle_col_base: 'Base',
|
||||
manual_settle_col_share: 'Share',
|
||||
manual_settle_col_gross: 'Gross',
|
||||
manual_settle_col_commission_share: 'Share%',
|
||||
manual_settle_col_net_short: 'Net',
|
||||
manual_settle_col_fee: 'Fee',
|
||||
manual_settle_col_net: 'Net',
|
||||
affiliate_share_rate: 'affiliate_share_rate',
|
||||
affiliate_fee_rate: 'affiliate_fee_rate',
|
||||
affiliate_contract_no: 'affiliate_contract_no',
|
||||
@@ -72,6 +85,19 @@ export default {
|
||||
manual_settle_commission_amount: 'Commission amount',
|
||||
manual_settle_submit: 'Settle',
|
||||
manual_settle_remark: 'Remark',
|
||||
manual_settle_calc_title: 'Settlement calculation',
|
||||
manual_settle_calc_intro_turnover_1: 'For this period, sum settled bets: total bet and total payout; platform PnL = bet − payout.',
|
||||
manual_settle_calc_intro_turnover_2: 'Channel commission = settlement base (total bet) × turnover share rate; the table below splits it by agent tree.',
|
||||
manual_settle_calc_intro_turnover_3: 'Commission applies only when platform-wide PnL is positive; otherwise channel commission is 0.',
|
||||
manual_settle_calc_intro_affiliate_1: 'For this period, affiliate commission is based on net player loss and ladder rules in this channel.',
|
||||
manual_settle_calc_intro_affiliate_2: 'Channel commission = base after costs × share rate; split in the table by agent hierarchy.',
|
||||
manual_settle_calc_intro_affiliate_3: 'If net loss is not positive, commission may be 0 — use preview amounts.',
|
||||
manual_settle_calc_tree_1: 'Expand/collapse the tree; each row is one agent share in this settlement.',
|
||||
manual_settle_calc_tree_2: 'Settlement base = amount from parent; share % = cut from parent pool; commission = gross before fee.',
|
||||
manual_settle_calc_tree_3: 'Commission share % = agent gross ÷ channel total commission × 100%.',
|
||||
manual_settle_calc_handling_fee: 'Net = gross − fee; fee uses channel rate {rate}% of gross, once per agent per period.',
|
||||
manual_settle_col_net: 'Net',
|
||||
manual_settle_split_scroll_tip: 'Swipe horizontally to view all columns',
|
||||
share_config: 'Share config',
|
||||
share_config_title: 'Channel admin share config',
|
||||
share_config_tip: 'Only enabled rows participate in settlement split, and enabled share total must equal 100%.',
|
||||
|
||||
@@ -9,7 +9,9 @@ export default {
|
||||
admin_username: '代理账号',
|
||||
commission_rate: '佣金比例',
|
||||
calc_base_amount: '结算基数',
|
||||
commission_amount: '佣金金额',
|
||||
commission_amount: '佣金金额(费前)',
|
||||
handling_fee: '手续费金额',
|
||||
net_commission_amount: '实发佣金',
|
||||
status: '状态',
|
||||
'status 0': '待发放',
|
||||
'status 1': '已发放',
|
||||
|
||||
@@ -15,6 +15,20 @@ export default {
|
||||
agent_mode_desc_affiliate_2: '计算方式:净客损扣除成本后 × 联营占成比例(affiliate_share_rate)。',
|
||||
agent_mode_desc_affiliate_3: '字段说明:可配置联营成本扣除比例与负结转余额;切换到该模式会自动清空返水比例。',
|
||||
turnover_share_rate: '返水分红比例',
|
||||
settlement_handling_fee: '代理结算手续费(%)',
|
||||
settlement_handling_fee_tip: '按该代理费前佣金的百分比扣除,每期每代理扣一次;手动结算时可单独调整',
|
||||
settlement_handling_fee_percent: '手续费(%)',
|
||||
settlement_handling_fee_amount: '手续费金额',
|
||||
manual_settle_settlement_base: '结算基数',
|
||||
manual_settle_net_commission: '实发佣金',
|
||||
manual_settle_col_base: '基数',
|
||||
manual_settle_col_share: '比例',
|
||||
manual_settle_col_gross: '佣金',
|
||||
manual_settle_col_commission_share: '占比',
|
||||
manual_settle_col_net_short: '实发',
|
||||
manual_settle_col_fee: '手续费',
|
||||
manual_settle_col_net: '实发',
|
||||
manual_settle_split_scroll_tip: '左右滑动查看全部分配列',
|
||||
affiliate_share_rate: '联营占成比例',
|
||||
affiliate_fee_rate: '联营成本扣除比例',
|
||||
affiliate_contract_no: '联营契约编号',
|
||||
@@ -72,6 +86,17 @@ export default {
|
||||
manual_settle_commission_amount: '佣金金额',
|
||||
manual_settle_submit: '结算',
|
||||
manual_settle_remark: '备注',
|
||||
manual_settle_calc_title: '结算计算说明',
|
||||
manual_settle_calc_intro_turnover_1: '本期统计区间内,汇总该渠道已结算注单的总投注额与总派彩额,平台盈亏 = 总投注 − 总派彩。',
|
||||
manual_settle_calc_intro_turnover_2: '渠道本期可分配佣金 = 结算基数(总投注额)× 返水分红比例;下方表格按代理树自上而下拆分该笔佣金。',
|
||||
manual_settle_calc_intro_turnover_3: '仅当平台大盘盈利时参与分红;若大盘亏损或持平,本期渠道佣金为 0。',
|
||||
manual_settle_calc_intro_affiliate_1: '本期统计区间内,按该渠道辖区净客损(平台盈亏)及联营规则计算可分配佣金。',
|
||||
manual_settle_calc_intro_affiliate_2: '渠道本期可分配佣金 = 扣除联营成本后的结算基数 × 阶梯占成比例;下方表格按代理树拆分。',
|
||||
manual_settle_calc_intro_affiliate_3: '净客损为负或持平时,本期可分配佣金可能为 0,以实际预览金额为准。',
|
||||
manual_settle_calc_tree_1: '分配树可点击展开/收起;每一行对应该代理在本期分到的金额。',
|
||||
manual_settle_calc_tree_2: '结算基数 = 上级代理结算后分给本代理的金额;分配比例 = 从上级佣金中抽取的百分比;佣金金额 = 本代理实得(费前)。',
|
||||
manual_settle_calc_tree_3: '佣金占比 = 该代理费前佣金 ÷ 渠道本期总佣金 × 100%,表示占全部可分配佣金的比例。',
|
||||
manual_settle_calc_handling_fee: '实发佣金 = 佣金金额 − 手续费金额;手续费按渠道配置 {rate}%(费前佣金比例)扣除,每期每代理扣一次。',
|
||||
share_config: '分配比例',
|
||||
share_config_title: '渠道管理员分配比例',
|
||||
share_config_tip: '仅启用项参与结算拆分,且启用项占比总和必须等于100%。',
|
||||
|
||||
@@ -109,6 +109,14 @@ const baTable = new baTableClass(
|
||||
operator: 'RANGE',
|
||||
formatter: formatAmount2,
|
||||
},
|
||||
{
|
||||
label: t('agent.commissionRecord.handling_fee'),
|
||||
prop: 'handling_fee',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
operator: 'RANGE',
|
||||
formatter: formatAmount2,
|
||||
},
|
||||
{
|
||||
label: t('agent.commissionRecord.status'),
|
||||
prop: 'status',
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<el-radio-button label="enabled">{{ t('channel.settle_filter_enabled') }}</el-radio-button>
|
||||
<el-radio-button label="disabled">{{ t('channel.settle_filter_disabled') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button v-if="adminInfo.super && auth('batchSettlePending')" type="warning" @click="onBatchSettlePending">
|
||||
<el-button v-if="auth('batchSettlePending')" type="warning" @click="onBatchSettlePending">
|
||||
{{ t('channel.batch_settle_pending') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -53,17 +53,26 @@
|
||||
<PopupForm />
|
||||
|
||||
<el-dialog
|
||||
class="ba-operate-dialog manual-settle-dialog"
|
||||
class="manual-settle-dialog"
|
||||
:close-on-click-modal="false"
|
||||
:model-value="manualSettle.visible"
|
||||
width="860px"
|
||||
:width="manualSettleDialogWidth"
|
||||
align-center
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@close="closeManualSettleDialog"
|
||||
@opened="dismissFloatingTooltips"
|
||||
>
|
||||
<template #header>
|
||||
<div class="title">{{ t('channel.manual_settle') }}</div>
|
||||
</template>
|
||||
<div v-loading="manualSettle.previewLoading" class="manual-settle-dialog-body">
|
||||
<el-form :model="manualSettle.form" label-width="140px" class="manual-settle-form">
|
||||
<el-form
|
||||
:model="manualSettle.form"
|
||||
:label-width="manualSettleFormLabelWidth"
|
||||
:label-position="manualSettleFormLabelPosition"
|
||||
class="manual-settle-form"
|
||||
>
|
||||
<el-form-item :label="t('channel.manual_settle_settlement_no')">
|
||||
<el-input v-model="manualSettle.form.settlement_no" readonly />
|
||||
</el-form-item>
|
||||
@@ -92,13 +101,97 @@
|
||||
<el-input v-model="manualSettle.form.commission_amount" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('channel.share_config')" class="manual-settle-form-item-full">
|
||||
<el-table :data="manualSettle.form.commission_split" border size="small" class="w100" max-height="220">
|
||||
<el-table-column prop="admin_username" :label="t('channel.admin__username')" min-width="100" />
|
||||
<el-table-column prop="share_rate" :label="t('channel.share_rate_percent')" min-width="90">
|
||||
<template #default="scope">{{ scope.row.share_rate }}%</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="commission_amount" :label="t('channel.manual_settle_commission_amount')" min-width="110" />
|
||||
</el-table>
|
||||
<div class="manual-settle-split-block">
|
||||
<div
|
||||
class="manual-settle-split-table-scroll"
|
||||
:class="{ 'is-mobile': manualSettleViewportMobile }"
|
||||
>
|
||||
<div
|
||||
class="manual-settle-split-table-inner"
|
||||
:class="{ 'is-mobile': manualSettleViewportMobile }"
|
||||
:style="manualSettleSplitInnerStyle"
|
||||
>
|
||||
<el-table
|
||||
:data="manualSettleSplitTree"
|
||||
row-key="admin_id"
|
||||
:tree-props="{ children: 'children' }"
|
||||
default-expand-all
|
||||
border
|
||||
size="small"
|
||||
:fit="!manualSettleViewportMobile"
|
||||
class="manual-settle-split-table"
|
||||
:class="{ 'is-mobile': manualSettleViewportMobile, 'w100': !manualSettleViewportMobile }"
|
||||
:table-layout="manualSettleTableLayout"
|
||||
>
|
||||
<el-table-column
|
||||
prop="admin_username"
|
||||
:label="t('channel.admin__username')"
|
||||
:min-width="manualSettleViewportMobile ? undefined : manualSettleSplitCol.adminWidth"
|
||||
:width="manualSettleViewportMobile ? manualSettleSplitCol.adminWidth : undefined"
|
||||
:show-overflow-tooltip="!manualSettleViewportMobile"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="settlement_base_amount"
|
||||
:label="t('channel.manual_settle_col_base')"
|
||||
:width="manualSettleSplitCol.baseWidth"
|
||||
align="center"
|
||||
:formatter="formatSplitAmountCell"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="share_rate"
|
||||
:label="t('channel.manual_settle_col_share')"
|
||||
:width="manualSettleSplitCol.shareWidth"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">{{ scope.row.share_rate }}%</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="commission_amount"
|
||||
:label="t('channel.manual_settle_col_gross')"
|
||||
:width="manualSettleSplitCol.grossWidth"
|
||||
align="center"
|
||||
:formatter="formatSplitAmountCell"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="commission_share_percent"
|
||||
:label="t('channel.manual_settle_col_commission_share')"
|
||||
:width="manualSettleSplitCol.commissionShareWidth"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">{{ formatCommissionSharePercent(scope.row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="handling_fee"
|
||||
:label="t('channel.manual_settle_col_fee')"
|
||||
:width="manualSettleSplitCol.feeWidth"
|
||||
align="center"
|
||||
:formatter="formatSplitAmountCell"
|
||||
/>
|
||||
<el-table-column
|
||||
:label="t('channel.manual_settle_col_net')"
|
||||
:width="manualSettleSplitCol.netWidth"
|
||||
align="center"
|
||||
>
|
||||
<template #default="scope">{{ formatNetCommission(scope.row) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<p v-if="manualSettleViewportMobile" class="manual-settle-split-scroll-tip">
|
||||
{{ 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>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('channel.manual_settle_remark')" class="manual-settle-form-item-full">
|
||||
<el-input v-model="manualSettle.form.remark" type="textarea" :rows="2" />
|
||||
@@ -328,13 +421,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, provide, reactive, ref, useTemplateRef } from 'vue'
|
||||
import { computed, nextTick, onMounted, onUnmounted, provide, reactive, ref, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import PopupForm from './popupForm.vue'
|
||||
import { baTableApi } from '/@/api/common'
|
||||
import { auth } from '/@/utils/common'
|
||||
import { useAdminInfo } from '/@/stores/adminInfo'
|
||||
import { defaultOptButtons } from '/@/components/table'
|
||||
import TableHeader from '/@/components/table/header/index.vue'
|
||||
import Table from '/@/components/table/index.vue'
|
||||
@@ -346,7 +438,76 @@ defineOptions({
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const adminInfo = useAdminInfo()
|
||||
|
||||
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 manualSettleFormLabelPosition = computed(() => (manualSettleViewportMobile.value ? 'top' : 'right'))
|
||||
const manualSettleFormLabelWidth = computed(() => (manualSettleViewportMobile.value ? 'auto' : '140px'))
|
||||
const manualSettleTableLayout = computed(() => 'fixed')
|
||||
const manualSettleSplitCol = computed(() => {
|
||||
if (manualSettleViewportMobile.value) {
|
||||
return {
|
||||
adminWidth: 120,
|
||||
baseWidth: 72,
|
||||
shareWidth: 58,
|
||||
grossWidth: 72,
|
||||
commissionShareWidth: 62,
|
||||
feeWidth: 64,
|
||||
netWidth: 72,
|
||||
}
|
||||
}
|
||||
return {
|
||||
adminWidth: 108,
|
||||
baseWidth: 76,
|
||||
shareWidth: 58,
|
||||
grossWidth: 72,
|
||||
commissionShareWidth: 62,
|
||||
feeWidth: 64,
|
||||
netWidth: 72,
|
||||
}
|
||||
})
|
||||
|
||||
const manualSettleSplitTableMinWidth = computed(() => {
|
||||
const col = manualSettleSplitCol.value
|
||||
return (
|
||||
col.adminWidth +
|
||||
col.baseWidth +
|
||||
col.shareWidth +
|
||||
col.grossWidth +
|
||||
col.commissionShareWidth +
|
||||
col.feeWidth +
|
||||
col.netWidth +
|
||||
32
|
||||
)
|
||||
})
|
||||
|
||||
const manualSettleSplitInnerStyle = computed(() => {
|
||||
if (!manualSettleViewportMobile.value) {
|
||||
return {}
|
||||
}
|
||||
return {
|
||||
width: `${manualSettleSplitTableMinWidth.value}px`,
|
||||
}
|
||||
})
|
||||
|
||||
const syncManualSettleViewport = () => {
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
manualSettleViewportMobile.value = window.innerWidth <= MANUAL_SETTLE_MOBILE_BREAKPOINT
|
||||
}
|
||||
|
||||
const dismissFloatingTooltips = () => {
|
||||
if (typeof document === 'undefined') {
|
||||
return
|
||||
}
|
||||
document.querySelectorAll('.el-popper[role="tooltip"]').forEach((node) => node.remove())
|
||||
const active = document.activeElement
|
||||
if (active instanceof HTMLElement) {
|
||||
active.blur()
|
||||
}
|
||||
}
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
let optButtons: OptButton[] = [
|
||||
{
|
||||
@@ -371,7 +532,7 @@ let optButtons: OptButton[] = [
|
||||
type: 'warning',
|
||||
icon: 'el-icon-Clock',
|
||||
class: 'table-row-manual-settle',
|
||||
disabledTip: false,
|
||||
disabledTip: true,
|
||||
display: () => auth('manualSettle'),
|
||||
click: (row: TableRow) => {
|
||||
void openManualSettleDialog(row)
|
||||
@@ -385,6 +546,33 @@ const formatAmountInt = (_row: any, _column: any, cellValue: number | string | n
|
||||
if (Number.isNaN(num)) return '-'
|
||||
return `${num}`
|
||||
}
|
||||
const formatCommissionSharePercent = (row: { commission_amount?: string | number; commission_share_percent?: string | number }) => {
|
||||
const preset = row.commission_share_percent
|
||||
if (preset !== null && preset !== undefined && preset !== '') {
|
||||
const n = Number(preset)
|
||||
if (Number.isFinite(n)) {
|
||||
return `${n.toFixed(2)}%`
|
||||
}
|
||||
}
|
||||
const total = Number(manualSettle.form.commission_amount ?? 0)
|
||||
const gross = Number(row.commission_amount ?? 0)
|
||||
if (!Number.isFinite(total) || total <= 0 || !Number.isFinite(gross) || gross <= 0) {
|
||||
return '0.00%'
|
||||
}
|
||||
return `${((gross / total) * 100).toFixed(2)}%`
|
||||
}
|
||||
|
||||
const formatSplitAmountCell = (_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)
|
||||
}
|
||||
|
||||
const formatAmount2 = (_row: any, _column: any, cellValue: number | string | null) => {
|
||||
if (cellValue === null || cellValue === undefined || cellValue === '') return '-'
|
||||
const num = Number(cellValue)
|
||||
@@ -434,7 +622,21 @@ const manualSettle = reactive({
|
||||
commission_rate: '',
|
||||
calc_base_amount: '',
|
||||
commission_amount: '',
|
||||
commission_split: [] as Array<{ admin_id: number; admin_username: string; share_rate: string; commission_amount: string }>,
|
||||
agent_mode: 'turnover' as string,
|
||||
settlement_handling_fee: '0.00',
|
||||
commission_split: [] as Array<{
|
||||
admin_id: number
|
||||
admin_username: string
|
||||
parent_admin_id?: number
|
||||
level?: number
|
||||
settlement_base_amount?: string
|
||||
share_rate: string
|
||||
commission_amount: string
|
||||
commission_share_percent?: string
|
||||
handling_fee_rate: number | string
|
||||
handling_fee?: string
|
||||
net_commission_amount?: string
|
||||
}>,
|
||||
remark: '',
|
||||
},
|
||||
})
|
||||
@@ -496,18 +698,105 @@ const resetManualSettleForm = () => {
|
||||
manualSettle.form.calc_base_amount = ''
|
||||
manualSettle.form.commission_amount = ''
|
||||
manualSettle.form.commission_split = []
|
||||
manualSettle.form.agent_mode = 'turnover'
|
||||
manualSettle.form.settlement_handling_fee = '0.00'
|
||||
manualSettle.form.remark = ''
|
||||
}
|
||||
|
||||
type ManualSettleSplitRow = {
|
||||
admin_id: number
|
||||
admin_username: string
|
||||
parent_admin_id?: number
|
||||
level?: number
|
||||
settlement_base_amount?: string
|
||||
share_rate: string
|
||||
commission_amount: string
|
||||
commission_share_percent?: string
|
||||
handling_fee_rate: number | string
|
||||
handling_fee?: string
|
||||
net_commission_amount?: string
|
||||
children?: ManualSettleSplitRow[]
|
||||
}
|
||||
|
||||
const buildCommissionSplitTree = (flat: ManualSettleSplitRow[]): ManualSettleSplitRow[] => {
|
||||
if (!flat.length) {
|
||||
return []
|
||||
}
|
||||
const nodeMap = new Map<number, ManualSettleSplitRow>()
|
||||
for (const row of flat) {
|
||||
nodeMap.set(row.admin_id, { ...row, children: [] })
|
||||
}
|
||||
const roots: ManualSettleSplitRow[] = []
|
||||
for (const row of flat) {
|
||||
const node = nodeMap.get(row.admin_id)
|
||||
if (!node) {
|
||||
continue
|
||||
}
|
||||
const parentId = Number(row.parent_admin_id ?? 0)
|
||||
if (parentId > 0 && nodeMap.has(parentId)) {
|
||||
const parent = nodeMap.get(parentId)
|
||||
if (parent) {
|
||||
if (!parent.children) {
|
||||
parent.children = []
|
||||
}
|
||||
parent.children.push(node)
|
||||
}
|
||||
continue
|
||||
}
|
||||
roots.push(node)
|
||||
}
|
||||
const pruneEmptyChildren = (nodes: ManualSettleSplitRow[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.children && node.children.length > 0) {
|
||||
pruneEmptyChildren(node.children)
|
||||
} else {
|
||||
delete node.children
|
||||
}
|
||||
}
|
||||
}
|
||||
pruneEmptyChildren(roots)
|
||||
return roots
|
||||
}
|
||||
|
||||
const manualSettleSplitTree = computed(() => buildCommissionSplitTree(manualSettle.form.commission_split))
|
||||
|
||||
const manualSettleCalcDescLines = computed(() => {
|
||||
const mode = manualSettle.form.agent_mode === 'affiliate' ? 'affiliate' : 'turnover'
|
||||
const feeRate = manualSettle.form.settlement_handling_fee
|
||||
const prefix =
|
||||
mode === 'affiliate'
|
||||
? [
|
||||
t('channel.manual_settle_calc_intro_affiliate_1'),
|
||||
t('channel.manual_settle_calc_intro_affiliate_2'),
|
||||
t('channel.manual_settle_calc_intro_affiliate_3'),
|
||||
]
|
||||
: [
|
||||
t('channel.manual_settle_calc_intro_turnover_1'),
|
||||
t('channel.manual_settle_calc_intro_turnover_2'),
|
||||
t('channel.manual_settle_calc_intro_turnover_3'),
|
||||
]
|
||||
return [
|
||||
...prefix,
|
||||
t('channel.manual_settle_calc_tree_1'),
|
||||
t('channel.manual_settle_calc_tree_2'),
|
||||
t('channel.manual_settle_calc_tree_3'),
|
||||
t('channel.manual_settle_calc_handling_fee', { rate: feeRate }),
|
||||
]
|
||||
})
|
||||
|
||||
const closeManualSettleDialog = () => {
|
||||
manualSettle.visible = false
|
||||
resetManualSettleForm()
|
||||
}
|
||||
|
||||
const openManualSettleDialog = async (row: TableRow) => {
|
||||
dismissFloatingTooltips()
|
||||
syncManualSettleViewport()
|
||||
manualSettle.channelId = row.id
|
||||
resetManualSettleForm()
|
||||
manualSettle.visible = true
|
||||
await nextTick()
|
||||
dismissFloatingTooltips()
|
||||
manualSettle.previewLoading = true
|
||||
try {
|
||||
const res = await createAxios(
|
||||
@@ -532,7 +821,9 @@ const openManualSettleDialog = async (row: TableRow) => {
|
||||
manualSettle.form.commission_rate = d.commission_rate ?? ''
|
||||
manualSettle.form.calc_base_amount = d.calc_base_amount ?? ''
|
||||
manualSettle.form.commission_amount = d.commission_amount ?? ''
|
||||
manualSettle.form.commission_split = Array.isArray(d.commission_split) ? d.commission_split : []
|
||||
manualSettle.form.agent_mode = d.agent_mode ?? 'turnover'
|
||||
manualSettle.form.settlement_handling_fee = d.settlement_handling_fee ?? '0.00'
|
||||
manualSettle.form.commission_split = normalizeManualSettleSplit(d.commission_split, d.settlement_handling_fee)
|
||||
manualSettle.form.remark = `${t('channel.manual_settle')}-CH${row.id}`
|
||||
} catch {
|
||||
manualSettle.visible = false
|
||||
@@ -541,6 +832,64 @@ const openManualSettleDialog = async (row: TableRow) => {
|
||||
}
|
||||
}
|
||||
|
||||
const calcHandlingFeeByPercent = (gross: number, ratePercent: number) => {
|
||||
const g = Number.isFinite(gross) && gross > 0 ? gross : 0
|
||||
let rate = Number.isFinite(ratePercent) ? ratePercent : 0
|
||||
if (rate < 0) rate = 0
|
||||
if (rate > 100) rate = 100
|
||||
return Math.round(g * rate * 100) / 10000
|
||||
}
|
||||
|
||||
const applyManualSettleRowAmounts = (row: {
|
||||
commission_amount?: string | number
|
||||
handling_fee_rate?: string | number
|
||||
handling_fee?: string | number
|
||||
net_commission_amount?: string
|
||||
}) => {
|
||||
const gross = Number(row.commission_amount ?? 0)
|
||||
const rate = Number(row.handling_fee_rate ?? 0)
|
||||
const feeAmount = calcHandlingFeeByPercent(gross, rate)
|
||||
row.handling_fee = feeAmount.toFixed(2)
|
||||
row.net_commission_amount = Math.max(0, (Number.isFinite(gross) ? gross : 0) - feeAmount).toFixed(2)
|
||||
}
|
||||
|
||||
const normalizeManualSettleSplit = (rows: unknown, defaultFeeRate: unknown) => {
|
||||
const feeDefault = Number(defaultFeeRate)
|
||||
const baseRate = Number.isFinite(feeDefault) && feeDefault >= 0 ? Math.min(100, feeDefault) : 0
|
||||
if (!Array.isArray(rows)) {
|
||||
return []
|
||||
}
|
||||
return rows.map((row: anyObj) => {
|
||||
let rate = Number(row.handling_fee_rate ?? row.handling_fee ?? baseRate)
|
||||
if (!Number.isFinite(rate) || rate < 0) {
|
||||
rate = baseRate
|
||||
}
|
||||
if (rate > 100) {
|
||||
rate = 100
|
||||
}
|
||||
const normalized = {
|
||||
...row,
|
||||
handling_fee_rate: rate,
|
||||
}
|
||||
applyManualSettleRowAmounts(normalized)
|
||||
return normalized
|
||||
})
|
||||
}
|
||||
|
||||
const formatNetCommission = (row: {
|
||||
commission_amount?: string | number
|
||||
handling_fee_rate?: string | number
|
||||
net_commission_amount?: string
|
||||
}) => {
|
||||
if (row.net_commission_amount !== undefined && row.net_commission_amount !== '') {
|
||||
return row.net_commission_amount
|
||||
}
|
||||
const gross = Number(row.commission_amount ?? 0)
|
||||
const rate = Number(row.handling_fee_rate ?? 0)
|
||||
const net = Math.max(0, (Number.isFinite(gross) ? gross : 0) - calcHandlingFeeByPercent(gross, rate))
|
||||
return net.toFixed(2)
|
||||
}
|
||||
|
||||
const submitManualSettle = async () => {
|
||||
if (!manualSettle.channelId) return
|
||||
manualSettle.loading = true
|
||||
@@ -552,6 +901,10 @@ const submitManualSettle = async () => {
|
||||
data: {
|
||||
id: manualSettle.channelId,
|
||||
remark: manualSettle.form.remark,
|
||||
commission_split: manualSettle.form.commission_split.map((row) => ({
|
||||
admin_id: row.admin_id,
|
||||
handling_fee_rate: row.handling_fee_rate ?? 0,
|
||||
})),
|
||||
},
|
||||
},
|
||||
{ showSuccessMessage: true }
|
||||
@@ -944,6 +1297,7 @@ const baTable = new baTableClass(
|
||||
defaultItems: {
|
||||
status: '1',
|
||||
agent_mode: 'turnover',
|
||||
settlement_handling_fee: 0,
|
||||
settle_cycle: 'weekly',
|
||||
settle_weekday: 1,
|
||||
settle_monthday: 1,
|
||||
@@ -976,6 +1330,8 @@ baTable.after.getData = () => {
|
||||
provide('baTable', baTable)
|
||||
|
||||
onMounted(() => {
|
||||
syncManualSettleViewport()
|
||||
window.addEventListener('resize', syncManualSettleViewport)
|
||||
baTable.table.ref = tableRef.value
|
||||
baTable.mount()
|
||||
baTable.getData()?.then(() => {
|
||||
@@ -984,6 +1340,10 @@ onMounted(() => {
|
||||
})
|
||||
void loadSettleStats()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', syncManualSettleViewport)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -1119,16 +1479,59 @@ onMounted(() => {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* 手动结算弹窗:可滚动(勿使用 ba-operate-dialog 固定 58vh 高度) */
|
||||
:deep(.manual-settle-dialog.el-dialog) {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
width: 92% !important;
|
||||
max-width: 860px;
|
||||
max-height: 92vh;
|
||||
margin: 4vh auto !important;
|
||||
padding-bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.manual-settle-dialog .el-dialog__header) {
|
||||
flex-shrink: 0;
|
||||
padding: 12px 16px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
:deep(.manual-settle-dialog .el-dialog__body) {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
height: auto !important;
|
||||
max-height: calc(92vh - 120px);
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior: contain;
|
||||
padding: 12px 16px 16px;
|
||||
}
|
||||
|
||||
:deep(.manual-settle-dialog .el-dialog__footer) {
|
||||
flex-shrink: 0;
|
||||
position: static;
|
||||
width: auto;
|
||||
box-shadow: none;
|
||||
border-top: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.manual-settle-dialog-body {
|
||||
max-height: min(70vh, 680px);
|
||||
overflow: auto;
|
||||
padding-right: 2px;
|
||||
min-height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
:deep(.manual-settle-dialog-body .el-loading-mask) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.manual-settle-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
column-gap: 16px;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.manual-settle-form :deep(.el-form-item) {
|
||||
@@ -1137,6 +1540,127 @@ onMounted(() => {
|
||||
|
||||
.manual-settle-form-item-full {
|
||||
grid-column: 1 / -1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.manual-settle-form-item-full :deep(.el-form-item__content) {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.manual-settle-split-block {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.manual-settle-split-table-scroll {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.manual-settle-split-table-scroll.is-mobile {
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
overscroll-behavior-x: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-x;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 6px;
|
||||
background: var(--el-fill-color-blank);
|
||||
}
|
||||
|
||||
.manual-settle-split-table-inner.is-mobile {
|
||||
display: inline-block;
|
||||
max-width: none;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.manual-settle-split-table-scroll.is-mobile :deep(.el-table) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.manual-settle-split-table-scroll.is-mobile :deep(.el-table__inner-wrapper) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.manual-settle-split-table-scroll.is-mobile :deep(.el-table__body-wrapper),
|
||||
.manual-settle-split-table-scroll.is-mobile :deep(.el-table__header-wrapper) {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.manual-settle-split-table-scroll.is-mobile :deep(.el-scrollbar__wrap) {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.manual-settle-split-table-scroll.is-mobile :deep(.el-scrollbar__bar.is-horizontal) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.manual-settle-split-scroll-tip {
|
||||
margin: 6px 0 0;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
color: var(--el-text-color-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.manual-settle-split-table:not(.is-mobile) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.manual-settle-split-table :deep(.el-table__header th.el-table__cell),
|
||||
.manual-settle-split-table :deep(.el-table__body td.el-table__cell) {
|
||||
padding: 6px 4px;
|
||||
}
|
||||
|
||||
.manual-settle-split-table :deep(.el-table__header .cell),
|
||||
.manual-settle-split-table :deep(.el-table__body .cell) {
|
||||
padding: 0 2px;
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.manual-settle-split-table :deep(.el-table__header .cell) {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.manual-settle-split-table.is-mobile :deep(.el-table__header th.el-table__cell),
|
||||
.manual-settle-split-table.is-mobile :deep(.el-table__body td.el-table__cell) {
|
||||
padding: 5px 3px;
|
||||
}
|
||||
|
||||
.manual-settle-split-table.is-mobile :deep(.el-table__header .cell),
|
||||
.manual-settle-split-table.is-mobile :deep(.el-table__body .cell) {
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
.manual-settle-split-table.is-mobile :deep(.el-table__body td:first-child .cell) {
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
.manual-settle-calc-alert {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.manual-settle-calc-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
line-height: 1.6;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.manual-settle-calc-list li + li {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.manual-settle-footer {
|
||||
@@ -1207,6 +1731,82 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:deep(.manual-settle-dialog.el-dialog) {
|
||||
width: 96% !important;
|
||||
max-width: none;
|
||||
max-height: 94vh;
|
||||
margin: 3vh auto !important;
|
||||
}
|
||||
|
||||
:deep(.manual-settle-dialog .el-dialog__header) {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
:deep(.manual-settle-dialog .el-dialog__body) {
|
||||
max-height: calc(94vh - 96px);
|
||||
padding: 10px 10px 12px;
|
||||
}
|
||||
|
||||
:deep(.manual-settle-dialog .el-dialog__footer) {
|
||||
padding: 8px 10px 10px;
|
||||
}
|
||||
|
||||
.manual-settle-form :deep(.el-form-item) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.manual-settle-form :deep(.el-form-item__label) {
|
||||
padding-bottom: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.manual-settle-form-item-full :deep(.el-form-item__content) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.manual-settle-split-block {
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.manual-settle-split-table-scroll.is-mobile {
|
||||
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-list {
|
||||
font-size: 12px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.manual-settle-footer {
|
||||
flex-wrap: wrap;
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.manual-settle-footer .el-button {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.channel-record-dialog.el-dialog) {
|
||||
width: 96% !important;
|
||||
max-height: 94vh;
|
||||
|
||||
@@ -69,6 +69,14 @@
|
||||
:input-attr="{ step: 0.01, precision: 2, min: 0, max: 100 }"
|
||||
:placeholder="`${t('Please input field', { field: t('channel.turnover_share_rate') })} (例如 30.5)`"
|
||||
/>
|
||||
<FormItem
|
||||
:label="t('channel.settlement_handling_fee')"
|
||||
type="number"
|
||||
v-model="baTable.form.items!.settlement_handling_fee"
|
||||
prop="settlement_handling_fee"
|
||||
:input-attr="{ step: 0.01, precision: 2, min: 0, max: 100 }"
|
||||
:placeholder="t('channel.settlement_handling_fee_tip')"
|
||||
/>
|
||||
<FormItem
|
||||
v-if="currentAgentMode === 'affiliate'"
|
||||
:label="t('channel.affiliate_contract_no')"
|
||||
|
||||
Reference in New Issue
Block a user