1.优化分红方式

This commit is contained in:
2026-05-29 12:01:00 +08:00
parent f286fcc56f
commit d8673fb2c5
13 changed files with 457 additions and 60 deletions

View File

@@ -18,6 +18,13 @@ export default {
'Manage subordinate agents here':
'Manage your subordinate agents here. You can only see yourself and your downline, not sub-agents under other agents.',
'Parent admin placeholder': 'Leave empty for top-level channel agent',
'Top level group parent hint':
'The selected role group is top-level; no parent agent is required. Settlement uses the channel commission share configured here.',
'Top level share formula hint':
'For a top-level role group, this rate is the share (%) of the channel period total commission. Amount = channel period total × this rate; sub-agents still take their share from their parents received amount.',
'Channel root share remainder hint':
'Other top-level agents on this channel already use {used}%; remaining allocatable {remaining}%. After saving this value, about {after}% remains on the channel.',
'Channel root share remainder none': 'Top-level shares on this channel already total 100%; no remaining allocatable share.',
'Share remainder hint':
'Siblings already use {used}%; remaining allocatable {remaining}%. After saving this value, parent keeps about {after}%.',
'Share remainder none for parent': 'Sibling shares total 100%; the parent will keep no commission at this level.',

View File

@@ -17,6 +17,11 @@ export default {
'Administrator login': '管理员登录名',
'Manage subordinate agents here': '在此管理您下级代理管理员;仅显示您本人及所有下级,无法查看其他代理线下的子代理。',
'Parent admin placeholder': '留空表示渠道顶级代理',
'Top level group parent hint': '当前角色组为顶级角色组,无需绑定上级代理;系统将按渠道分红比例直接结算至该管理员。',
'Top level share formula hint':
'顶级角色组分红比例表示从渠道本期总佣金中分得的比例(%)。实得 = 渠道本期总佣金 × 本项分红比例;下级子代理仍从其上级实得中按各自比例抽取。',
'Channel root share remainder hint': '同渠道顶级代理已分配 {used}%,当前剩余可设 {remaining}%;若本项设为当前值,保存后同渠道约剩 {after}%。',
'Channel root share remainder none': '同渠道顶级代理比例已合计 100%,无法再分配。',
'Share remainder hint': '同上级下已分配 {used}%,当前剩余可设 {remaining}%;若本项设为当前值,保存后上级约剩 {after}%。',
'Share remainder none for parent': '同上级下子代理比例已合计 100%,上级在本层将无分红留存。',
'Share remainder none after current': '当前比例将超过剩余可分配额度,请调低后再保存。',

View File

@@ -78,10 +78,14 @@
remoteUrl: '/admin/auth.Admin/index',
field: 'username',
pk: 'id',
disabled: isTopLevelGroup,
params: parentSelectParams,
placeholder: t('auth.admin.Parent admin placeholder'),
}"
/>
<el-form-item v-if="showParentField && isTopLevelGroup" label=" ">
<el-alert :title="t('auth.admin.Top level group parent hint')" type="info" :closable="false" show-icon />
</el-form-item>
<FormItem
v-if="showShareRateField"
:label="t('auth.admin.commission_share_rate')"
@@ -96,6 +100,9 @@
class: 'w100',
}"
/>
<el-form-item v-if="showShareRateField && isTopLevelGroup" label=" ">
<el-alert :title="t('auth.admin.Top level share formula hint')" type="info" :closable="false" show-icon />
</el-form-item>
<el-form-item v-if="showShareRateField && shareHint" label=" ">
<el-alert :title="shareHint" :type="shareHintType" :closable="false" show-icon />
</el-form-item>
@@ -185,6 +192,7 @@ const { t } = useI18n()
const shareHint = ref('')
const shareHintType = ref<'info' | 'warning'>('info')
const isTopLevelGroup = ref(false)
const isSelfEdit = computed(() => baTable.form.operate === 'Edit' && adminInfo.id == baTable.form.items?.id)
@@ -194,6 +202,9 @@ const showParentField = computed(() => adminInfo.super && !isSelfEdit.value)
const showShareRateField = computed(() => {
if (isSelfEdit.value) return false
if (isTopLevelGroup.value) {
return adminInfo.super || baTable.form.operate === 'Add'
}
if (adminInfo.super) {
const pid = baTable.form.items?.parent_admin_id
return pid !== null && pid !== undefined && pid !== '' && Number(pid) > 0
@@ -238,11 +249,87 @@ const singleGroupValue = computed({
},
})
const resolveChannelIdForShare = (): number => {
const cid = baTable.form.items?.channel_id
if (cid !== null && cid !== undefined && cid !== '') {
return Number(cid)
}
return 0
}
const loadGroupMeta = async (groupId: unknown) => {
if (groupId === null || groupId === undefined || groupId === '') {
isTopLevelGroup.value = false
return
}
try {
const res = await createAxios({
url: '/admin/auth.Admin/groupMeta',
method: 'get',
params: { group_id: groupId },
})
isTopLevelGroup.value = !!res.data.is_top_level
if (isTopLevelGroup.value && baTable.form.items) {
baTable.form.items.parent_admin_id = null
const metaChannelId = res.data.channel_id
if (
adminInfo.super &&
(baTable.form.items.channel_id === null ||
baTable.form.items.channel_id === undefined ||
baTable.form.items.channel_id === '') &&
metaChannelId !== null &&
metaChannelId !== undefined &&
metaChannelId !== ''
) {
baTable.form.items.channel_id = metaChannelId
}
}
} catch {
isTopLevelGroup.value = false
}
}
const loadShareRemainder = async () => {
if (!showShareRateField.value) {
shareHint.value = ''
return
}
if (isTopLevelGroup.value) {
const channelId = resolveChannelIdForShare()
try {
const res = await createAxios({
url: '/admin/auth.Admin/commissionShareRemainder',
method: 'get',
params: {
is_top_level: 1,
channel_id: channelId,
exclude_id: baTable.form.items?.id || 0,
},
})
const remaining = res.data.remaining_rate ?? '100.00'
const used = res.data.used_rate ?? '0.00'
const current = baTable.form.items?.commission_share_rate
let afterCurrent = remaining
if (current !== null && current !== undefined && current !== '') {
afterCurrent = (Number(remaining) - Number(current)).toFixed(2)
}
shareHint.value = t('auth.admin.Channel root share remainder hint', {
used,
remaining,
after: afterCurrent,
})
shareHintType.value = Number(remaining) <= 0 || Number(afterCurrent) < 0 ? 'warning' : 'info'
if (Number(remaining) <= 0) {
shareHint.value = t('auth.admin.Channel root share remainder none')
} else if (Number(afterCurrent) <= 0 && current) {
shareHint.value = t('auth.admin.Share remainder none after current')
}
} catch {
shareHint.value = ''
}
return
}
let parentId = baTable.form.items?.parent_admin_id
if (!adminInfo.super) {
parentId = adminInfo.id
@@ -284,17 +371,41 @@ const loadShareRemainder = async () => {
}
watch(
() => [baTable.form.items?.parent_admin_id, baTable.form.items?.commission_share_rate, baTable.form.operate],
() => singleGroupValue.value,
(groupId) => {
void loadGroupMeta(groupId)
},
{ immediate: true }
)
watch(
() => [
baTable.form.items?.parent_admin_id,
baTable.form.items?.commission_share_rate,
baTable.form.items?.channel_id,
baTable.form.operate,
isTopLevelGroup.value,
],
() => {
void loadShareRemainder()
},
{ immediate: true }
)
watch(
() => baTable.form.items?.primary_group_is_top_level,
(val) => {
if (typeof val === 'boolean') {
isTopLevelGroup.value = val
}
},
{ immediate: true }
)
watch(
() => baTable.form.items?.share_remainder,
(val) => {
if (val && baTable.form.operate === 'Edit') {
if (val && baTable.form.operate === 'Edit' && !isTopLevelGroup.value) {
shareHint.value = t('auth.admin.Share remainder hint', {
used: val.used_rate ?? '0.00',
remaining: val.remaining_rate ?? '100.00',
@@ -305,6 +416,26 @@ watch(
{ immediate: true }
)
watch(
() => baTable.form.items?.root_share_remainder,
(val) => {
if (val && baTable.form.operate === 'Edit' && isTopLevelGroup.value) {
shareHint.value = t('auth.admin.Channel root share remainder hint', {
used: val.used_rate ?? '0.00',
remaining: val.remaining_rate ?? '100.00',
after: val.remaining_rate ?? '100.00',
})
}
},
{ immediate: true }
)
watch(isTopLevelGroup, (topLevel) => {
if (topLevel && baTable.form.items) {
baTable.form.items.parent_admin_id = null
}
})
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
username: [buildValidatorData({ name: 'required', title: t('auth.admin.username') }), buildValidatorData({ name: 'account' })],
nickname: [buildValidatorData({ name: 'required', title: t('auth.admin.nickname') })],