fix(admin): 主站分销管理按最新分成与释放口径改展示
规则页按模式展示收号/卖出/服务费,通道费标明主站承担;订单改为释放状态筛选;结算区分自动与人工批次。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -145,13 +145,15 @@ type DistributionOrderDTO struct {
|
||||
}
|
||||
|
||||
type DistributionOrderListQuery struct {
|
||||
SiteID uint64
|
||||
Kind string
|
||||
Role string
|
||||
Status string
|
||||
Keyword string
|
||||
Page int
|
||||
Size int
|
||||
SiteID uint64
|
||||
Kind string
|
||||
Role string
|
||||
Status string
|
||||
Mode string
|
||||
FinancialStatus string
|
||||
Keyword string
|
||||
Page int
|
||||
Size int
|
||||
}
|
||||
|
||||
type DistributionOrderPage struct {
|
||||
|
||||
@@ -144,13 +144,15 @@ func (h *Handler) ListOrders(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
result, err := h.service.ListOrders(c.Request.Context(), DistributionOrderListQuery{
|
||||
SiteID: scoped,
|
||||
Kind: c.Query("kind"),
|
||||
Role: c.Query("role"),
|
||||
Status: c.Query("status"),
|
||||
Keyword: c.Query("keyword"),
|
||||
Page: page,
|
||||
Size: size,
|
||||
SiteID: scoped,
|
||||
Kind: c.Query("kind"),
|
||||
Role: c.Query("role"),
|
||||
Status: c.Query("status"),
|
||||
Mode: c.Query("mode"),
|
||||
FinancialStatus: c.Query("financial_status"),
|
||||
Keyword: c.Query("keyword"),
|
||||
Page: page,
|
||||
Size: size,
|
||||
})
|
||||
if err != nil {
|
||||
writeError(c, err)
|
||||
|
||||
@@ -331,9 +331,16 @@ func (r *Repository) UpdateSite(ctx context.Context, adminID, id uint64, req Upd
|
||||
}
|
||||
|
||||
func (r *Repository) ListRules(ctx context.Context, adminID uint64, siteID *uint64, mode string) ([]RuleDTO, error) {
|
||||
db := r.db.WithContext(ctx).Model(&model.DistributionRuleVersion{}).Order("effective_from DESC, id DESC")
|
||||
db := r.db.WithContext(ctx).Model(&model.DistributionRuleVersion{}).
|
||||
Order("CASE WHEN site_id IS NULL THEN 0 ELSE 1 END ASC, site_id ASC, mode ASC, version DESC, id DESC")
|
||||
if siteID == nil {
|
||||
db = db.Where("site_id IS NULL")
|
||||
global, err := r.hasGlobalDistributionAccess(ctx, adminID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !global {
|
||||
db = db.Where("site_id IS NULL")
|
||||
}
|
||||
} else {
|
||||
if err := r.ensureSiteAccess(ctx, adminID, *siteID); err != nil {
|
||||
return nil, err
|
||||
@@ -475,6 +482,12 @@ func (r *Repository) ListOrders(ctx context.Context, query DistributionOrderList
|
||||
if query.Status != "" {
|
||||
base = base.Where("ro.status = ?", query.Status)
|
||||
}
|
||||
if mode := strings.TrimSpace(query.Mode); mode != "" {
|
||||
base = base.Where("ro.distribution_mode = ?", mode)
|
||||
}
|
||||
if financialStatus := strings.TrimSpace(query.FinancialStatus); financialStatus != "" {
|
||||
base = base.Where("f.settlement_status = ?", financialStatus)
|
||||
}
|
||||
if strings.TrimSpace(query.Keyword) != "" {
|
||||
kw := "%" + strings.TrimSpace(query.Keyword) + "%"
|
||||
base = base.Where("(ro.order_no LIKE ? OR rl.listing_no LIKE ? OR ga.title LIKE ? OR u.phone LIKE ?)", kw, kw, kw, kw)
|
||||
|
||||
@@ -42,7 +42,11 @@ export interface DistributionOrder {
|
||||
status: string
|
||||
refund_status: string
|
||||
settlement_status: string
|
||||
distributor_settlement_status?: string
|
||||
release_at?: string
|
||||
distribution_mode: string
|
||||
gross_paid_amount_cent: number
|
||||
refund_amount_cent: number
|
||||
net_paid_amount_cent: number
|
||||
payment_fee_cent: number
|
||||
service_fee_cent: number
|
||||
@@ -194,6 +198,8 @@ export async function updateDistributionRuleStatus(id: number, status: string) {
|
||||
export async function fetchDistributionOrders(params: {
|
||||
kind?: string
|
||||
status?: string
|
||||
mode?: string
|
||||
financialStatus?: string
|
||||
siteID?: number
|
||||
page?: number
|
||||
pageSize?: number
|
||||
@@ -204,6 +210,8 @@ export async function fetchDistributionOrders(params: {
|
||||
params: {
|
||||
kind: params.kind || undefined,
|
||||
status: params.status || undefined,
|
||||
mode: params.mode || undefined,
|
||||
financial_status: params.financialStatus || undefined,
|
||||
site_id: params.siteID || undefined,
|
||||
page: params.page || 1,
|
||||
page_size: params.pageSize || 20,
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { DistributionRule } from '@/features/admin/api/adminDistribution'
|
||||
|
||||
export const DISTRIBUTION_MODES = [
|
||||
{
|
||||
value: 'two_party_distributor_receive',
|
||||
label: '两方 · 分站收号',
|
||||
hint: '主站下单,货在分站',
|
||||
},
|
||||
{
|
||||
value: 'two_party_platform_receive',
|
||||
label: '两方 · 分站卖出',
|
||||
hint: '分站卖主站货',
|
||||
},
|
||||
{
|
||||
value: 'three_party',
|
||||
label: '三方分账',
|
||||
hint: '分站卖另一分站的货',
|
||||
},
|
||||
] as const
|
||||
|
||||
const MODE_LABELS: Record<string, string> = Object.fromEntries(
|
||||
DISTRIBUTION_MODES.map(item => [item.value, item.label])
|
||||
)
|
||||
|
||||
const PARTY_ROLE_LABELS: Record<string, string> = {
|
||||
distributor_receive: '分站收号',
|
||||
platform_sell: '主站卖出',
|
||||
platform_receive: '主站收号',
|
||||
distributor_sell: '分站卖出',
|
||||
distributor_a: '收号站',
|
||||
distributor_b: '卖出站',
|
||||
}
|
||||
|
||||
export function distributionModeLabel(mode?: string) {
|
||||
if (!mode) return '-'
|
||||
return MODE_LABELS[mode] || mode
|
||||
}
|
||||
|
||||
export function distributionPartyRoleLabel(role?: string) {
|
||||
if (!role) return '-'
|
||||
return PARTY_ROLE_LABELS[role] || role
|
||||
}
|
||||
|
||||
export function formatRatioPercent(value?: number) {
|
||||
const n = Number(value || 0)
|
||||
return `${n.toFixed(n % 1 === 0 ? 0 : 1)}%`
|
||||
}
|
||||
|
||||
export function formatPaymentFeeBps(bps?: number) {
|
||||
return `${Number(bps || 0) / 100}%`
|
||||
}
|
||||
|
||||
export function ruleSplitParts(rule: DistributionRule) {
|
||||
switch (rule.mode) {
|
||||
case 'two_party_distributor_receive':
|
||||
return [
|
||||
{ label: '分站收号', value: formatRatioPercent(rule.distributor_receive_ratio) },
|
||||
{ label: '主站卖出', value: formatRatioPercent(rule.platform_sell_ratio) },
|
||||
]
|
||||
case 'two_party_platform_receive':
|
||||
return [
|
||||
{ label: '主站收号', value: formatRatioPercent(rule.platform_receive_ratio) },
|
||||
{ label: '分站卖出', value: formatRatioPercent(rule.distributor_sell_ratio) },
|
||||
]
|
||||
case 'three_party':
|
||||
return [
|
||||
{ label: '收号站', value: formatRatioPercent(rule.distributor_a_ratio) },
|
||||
{ label: '卖出站', value: formatRatioPercent(rule.distributor_b_ratio) },
|
||||
]
|
||||
default:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export function formatServiceFee(
|
||||
rule: Pick<DistributionRule, 'service_fee_value' | 'service_fee_unit'>
|
||||
) {
|
||||
if (rule.service_fee_unit === 'fixed_amount') {
|
||||
return `${Number(rule.service_fee_value || 0).toFixed(2)} 元/单`
|
||||
}
|
||||
return formatRatioPercent(rule.service_fee_value)
|
||||
}
|
||||
|
||||
export function settlementBatchKind(batchNo?: string) {
|
||||
if (batchNo?.startsWith('DA')) {
|
||||
return { label: '自动释放', type: 'success' as const }
|
||||
}
|
||||
if (batchNo?.startsWith('DS')) {
|
||||
return { label: '人工批次', type: 'warning' as const }
|
||||
}
|
||||
return { label: '结算批次', type: 'info' as const }
|
||||
}
|
||||
|
||||
export function distributorReleaseLabel(row: {
|
||||
distributor_settlement_status?: string
|
||||
release_at?: string
|
||||
}) {
|
||||
if (row.distributor_settlement_status === 'settled') {
|
||||
return { label: '已释放入账', type: 'success' as const }
|
||||
}
|
||||
if (row.distributor_settlement_status === 'pending' && row.release_at) {
|
||||
const date = new Date(row.release_at)
|
||||
const text = Number.isNaN(date.getTime())
|
||||
? '待释放'
|
||||
: `待释放 ${date.toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' })}`
|
||||
return { label: text, type: 'warning' as const }
|
||||
}
|
||||
if (row.distributor_settlement_status === 'pending') {
|
||||
return { label: '待释放', type: 'info' as const }
|
||||
}
|
||||
return { label: '未进入释放', type: 'info' as const }
|
||||
}
|
||||
@@ -7,8 +7,14 @@ import {
|
||||
type DistributionOrder,
|
||||
type DistributionSite,
|
||||
} from '@/features/admin/api/adminDistribution'
|
||||
import {
|
||||
DISTRIBUTION_MODES,
|
||||
distributionModeLabel,
|
||||
distributorReleaseLabel,
|
||||
} from '@/features/admin/utils/distributionLabels'
|
||||
import { formatCent } from '@/shared/utils/money'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
import { orderStatusLabel, refundStatusLabel } from '@/shared/utils/statusLabels'
|
||||
import { readError } from '@/shared/utils/error'
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -18,18 +24,13 @@ const total = ref(0)
|
||||
|
||||
const query = reactive({
|
||||
siteID: undefined as number | undefined,
|
||||
kind: '' as string,
|
||||
status: '' as string,
|
||||
mode: '',
|
||||
status: '',
|
||||
financialStatus: '',
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
})
|
||||
|
||||
const modeMap: Record<string, string> = {
|
||||
two_party_distributor_receive: '收号',
|
||||
two_party_platform_receive: '售出',
|
||||
three_party: '三方',
|
||||
}
|
||||
|
||||
async function loadSites() {
|
||||
try {
|
||||
sites.value = await fetchDistributionSites()
|
||||
@@ -43,8 +44,9 @@ async function loadOrders() {
|
||||
try {
|
||||
const res = await fetchDistributionOrders({
|
||||
siteID: query.siteID,
|
||||
kind: query.kind || undefined,
|
||||
mode: query.mode || undefined,
|
||||
status: query.status || undefined,
|
||||
financialStatus: query.financialStatus || undefined,
|
||||
page: query.page,
|
||||
pageSize: query.pageSize,
|
||||
})
|
||||
@@ -64,15 +66,16 @@ function handleSearch() {
|
||||
|
||||
function handleReset() {
|
||||
query.siteID = undefined
|
||||
query.kind = ''
|
||||
query.mode = ''
|
||||
query.status = ''
|
||||
query.financialStatus = ''
|
||||
query.page = 1
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
function getSiteName(siteId: number) {
|
||||
const site = sites.value.find(s => s.id === siteId)
|
||||
return site ? `${site.site_name}` : `站点 #${siteId}`
|
||||
return site ? site.site_name : `站点 #${siteId}`
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -85,9 +88,9 @@ onMounted(async () => {
|
||||
<div class="distribution-orders-view">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2 class="page-title">分销订单监控</h2>
|
||||
<h2 class="page-title">分销订单</h2>
|
||||
<p class="page-subtitle">
|
||||
全览各合作分销站点的收号与售出订单明细、渠道手续费、单笔服务费及利润分成流水。
|
||||
查看分销订单的利润基数、主站承担的通道费,以及冷却期释放状态。不要把订单履约结算和分销入账混为一谈。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -104,17 +107,33 @@ onMounted(async () => {
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="交易类型">
|
||||
<el-select v-model="query.kind" clearable placeholder="全部模式" style="width: 140px">
|
||||
<el-option label="分销收号" value="receive" />
|
||||
<el-option label="分销售出" value="sold" />
|
||||
<el-option label="全部" value="all" />
|
||||
<el-form-item label="分销模式">
|
||||
<el-select v-model="query.mode" clearable placeholder="全部模式" style="width: 180px">
|
||||
<el-option
|
||||
v-for="item in DISTRIBUTION_MODES"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="结算状态">
|
||||
<el-select v-model="query.status" clearable placeholder="全部状态" style="width: 140px">
|
||||
<el-option label="待结算" value="pending" />
|
||||
<el-option label="已结算" value="settled" />
|
||||
<el-form-item label="订单状态">
|
||||
<el-select v-model="query.status" clearable placeholder="全部" style="width: 140px">
|
||||
<el-option label="已完成" value="completed" />
|
||||
<el-option label="使用中" value="renting" />
|
||||
<el-option label="申诉中" value="disputing" />
|
||||
<el-option label="已关闭" value="closed" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="释放状态">
|
||||
<el-select
|
||||
v-model="query.financialStatus"
|
||||
clearable
|
||||
placeholder="全部"
|
||||
style="width: 140px"
|
||||
>
|
||||
<el-option label="待释放" value="pending" />
|
||||
<el-option label="已释放入账" value="settled" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -127,53 +146,62 @@ onMounted(async () => {
|
||||
<el-card shadow="never" style="margin-top: 16px">
|
||||
<el-table v-loading="loading" :data="orders" stripe style="width: 100%">
|
||||
<el-table-column label="订单号" prop="order_no" min-width="160" />
|
||||
<el-table-column label="分销站点" min-width="140">
|
||||
<el-table-column label="分销站点" min-width="130">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small">{{ getSiteName(row.site_id) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="模式" width="90" align="center">
|
||||
<el-table-column label="模式" min-width="140">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info" size="small">
|
||||
{{ modeMap[row.distribution_mode] || row.distribution_mode }}
|
||||
</el-tag>
|
||||
{{ distributionModeLabel(row.distribution_mode) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单实付" min-width="110" align="right">
|
||||
<el-table-column label="净付款" min-width="100" align="right">
|
||||
<template #default="{ row }">
|
||||
<strong>{{ formatCent(row.net_paid_amount_cent) }}</strong>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="渠道手续费" min-width="100" align="right">
|
||||
<el-table-column label="分站加价" min-width="100" align="right">
|
||||
<template #default="{ row }">
|
||||
{{ formatCent(row.payment_fee_cent) }}
|
||||
{{ formatCent(row.markup_amount_cent) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="固定服务费" min-width="100" align="right">
|
||||
<el-table-column label="利润基数" min-width="100" align="right">
|
||||
<template #default="{ row }">
|
||||
{{ formatCent(row.split_base_amount_cent) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="通道费" min-width="110" align="right">
|
||||
<template #default="{ row }">
|
||||
<div>{{ formatCent(row.payment_fee_cent) }}</div>
|
||||
<small class="muted">主站承担</small>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="服务费" min-width="90" align="right">
|
||||
<template #default="{ row }">
|
||||
{{ formatCent(row.service_fee_cent) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="税费" min-width="90" align="right">
|
||||
<template #default="{ row }">
|
||||
{{ formatCent(row.tax_amount_cent) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单状态" width="100" align="center">
|
||||
<el-table-column label="订单状态" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'completed' ? 'success' : 'warning'" size="small">
|
||||
{{ row.status }}
|
||||
{{ orderStatusLabel(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算状态" width="100" align="center">
|
||||
<el-table-column label="退款" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.settlement_status === 'settled' ? 'success' : 'info'" size="small">
|
||||
{{ row.settlement_status === 'settled' ? '已结算' : '待结算' }}
|
||||
<span>{{ refundStatusLabel(row.refund_status) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分销入账" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="distributorReleaseLabel(row).type" size="small">
|
||||
{{ distributorReleaseLabel(row).label }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" min-width="160">
|
||||
<el-table-column label="下单时间" min-width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.created_at) }}
|
||||
</template>
|
||||
@@ -215,6 +243,7 @@ onMounted(async () => {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
@@ -226,4 +255,9 @@ onMounted(async () => {
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: #9ca3af;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import {
|
||||
fetchDistributionRules,
|
||||
fetchDistributionSites,
|
||||
updateDistributionRuleStatus,
|
||||
createDistributionRule,
|
||||
type DistributionRule,
|
||||
type DistributionSite,
|
||||
type CreateRulePayload,
|
||||
} from '@/features/admin/api/adminDistribution'
|
||||
import {
|
||||
DISTRIBUTION_MODES,
|
||||
distributionModeLabel,
|
||||
formatPaymentFeeBps,
|
||||
formatServiceFee,
|
||||
ruleSplitParts,
|
||||
} from '@/features/admin/utils/distributionLabels'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
import { readError } from '@/shared/utils/error'
|
||||
|
||||
const loading = ref(false)
|
||||
const sites = ref<DistributionSite[]>([])
|
||||
const selectedSiteId = ref<number | undefined>(undefined)
|
||||
const selectedMode = ref('')
|
||||
const rules = ref<DistributionRule[]>([])
|
||||
|
||||
const modeMap: Record<string, string> = {
|
||||
two_party_distributor_receive: '两方-分销收号',
|
||||
two_party_platform_receive: '两方-分销卖出',
|
||||
three_party: '三方分销',
|
||||
}
|
||||
const createVisible = ref(false)
|
||||
const creating = ref(false)
|
||||
const createForm = reactive({
|
||||
site_id: undefined as number | undefined,
|
||||
mode: 'two_party_distributor_receive',
|
||||
receive_ratio: 35,
|
||||
sell_ratio: 40,
|
||||
service_fee_value: 25,
|
||||
payment_fee_bps: 50,
|
||||
tax_enabled: false,
|
||||
tax_rate_bps: 100,
|
||||
activate: true,
|
||||
})
|
||||
|
||||
const createModeMeta = computed(
|
||||
() => DISTRIBUTION_MODES.find(item => item.value === createForm.mode) ?? DISTRIBUTION_MODES[0]
|
||||
)
|
||||
|
||||
async function loadSites() {
|
||||
try {
|
||||
@@ -33,7 +56,10 @@ async function loadSites() {
|
||||
async function loadRules() {
|
||||
loading.value = true
|
||||
try {
|
||||
rules.value = await fetchDistributionRules(selectedSiteId.value)
|
||||
rules.value = await fetchDistributionRules(
|
||||
selectedSiteId.value,
|
||||
selectedMode.value || undefined
|
||||
)
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '获取分销规则列表失败'))
|
||||
} finally {
|
||||
@@ -41,6 +67,18 @@ async function loadRules() {
|
||||
}
|
||||
}
|
||||
|
||||
function getSiteName(siteId?: number) {
|
||||
if (!siteId) return '全局默认'
|
||||
const site = sites.value.find(s => s.id === siteId)
|
||||
return site ? `${site.site_name} (${site.site_code})` : `站点 #${siteId}`
|
||||
}
|
||||
|
||||
function statusMeta(status: string) {
|
||||
if (status === 'active') return { label: '生效中', type: 'success' as const }
|
||||
if (status === 'draft') return { label: '草稿', type: 'info' as const }
|
||||
return { label: '已停用', type: 'danger' as const }
|
||||
}
|
||||
|
||||
async function handleToggleStatus(row: DistributionRule) {
|
||||
const next = row.status === 'active' ? 'disabled' : 'active'
|
||||
try {
|
||||
@@ -52,10 +90,69 @@ async function handleToggleStatus(row: DistributionRule) {
|
||||
}
|
||||
}
|
||||
|
||||
function getSiteName(siteId?: number) {
|
||||
if (!siteId) return '全局默认'
|
||||
const site = sites.value.find(s => s.id === siteId)
|
||||
return site ? `${site.site_name} (${site.site_code})` : `站点 #${siteId}`
|
||||
function resetCreateForm() {
|
||||
createForm.site_id = selectedSiteId.value
|
||||
createForm.mode = selectedMode.value || 'two_party_distributor_receive'
|
||||
createForm.receive_ratio = 35
|
||||
createForm.sell_ratio = 40
|
||||
createForm.service_fee_value = 25
|
||||
createForm.payment_fee_bps = 50
|
||||
createForm.tax_enabled = false
|
||||
createForm.tax_rate_bps = 100
|
||||
createForm.activate = true
|
||||
}
|
||||
|
||||
function handleOpenCreate() {
|
||||
resetCreateForm()
|
||||
createVisible.value = true
|
||||
}
|
||||
|
||||
function buildCreatePayload(): CreateRulePayload {
|
||||
const payload: CreateRulePayload = {
|
||||
site_id: createForm.site_id || undefined,
|
||||
mode: createForm.mode,
|
||||
service_fee_value: Number(createForm.service_fee_value),
|
||||
service_fee_unit: 'percent',
|
||||
payment_fee_bps: Number(createForm.payment_fee_bps),
|
||||
tax_enabled: createForm.tax_enabled,
|
||||
tax_rate_bps: createForm.tax_enabled ? Number(createForm.tax_rate_bps) : 100,
|
||||
}
|
||||
if (createForm.mode === 'two_party_distributor_receive') {
|
||||
payload.distributor_receive_ratio = Number(createForm.receive_ratio)
|
||||
payload.platform_sell_ratio = Number(createForm.sell_ratio)
|
||||
} else if (createForm.mode === 'two_party_platform_receive') {
|
||||
payload.platform_receive_ratio = Number(createForm.receive_ratio)
|
||||
payload.distributor_sell_ratio = Number(createForm.sell_ratio)
|
||||
} else {
|
||||
payload.distributor_a_ratio = Number(createForm.receive_ratio)
|
||||
payload.distributor_b_ratio = Number(createForm.sell_ratio)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
async function handleCreate() {
|
||||
const total =
|
||||
Number(createForm.receive_ratio) +
|
||||
Number(createForm.sell_ratio) +
|
||||
Number(createForm.service_fee_value)
|
||||
if (Math.abs(total - 100) > 0.05) {
|
||||
ElMessage.warning(`收号 + 卖出 + 服务费应为 100%,当前为 ${total}%`)
|
||||
return
|
||||
}
|
||||
creating.value = true
|
||||
try {
|
||||
const created = await createDistributionRule(buildCreatePayload())
|
||||
if (createForm.activate) {
|
||||
await updateDistributionRuleStatus(created.id, 'active')
|
||||
}
|
||||
ElMessage.success(createForm.activate ? '规则已创建并启用' : '规则草稿已创建')
|
||||
createVisible.value = false
|
||||
await loadRules()
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '创建分销规则失败'))
|
||||
} finally {
|
||||
creating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -70,15 +167,15 @@ onMounted(async () => {
|
||||
<div>
|
||||
<h2 class="page-title">分销规则配置</h2>
|
||||
<p class="page-subtitle">
|
||||
配置与维护各分销站点的佣金分成比例、单笔服务费、支付通道手续费与税费规则。
|
||||
按模式配置利润分成。利润 = 净付款 − 号主 − 加价;通道费由主站承担,只记账不进分成。
|
||||
</p>
|
||||
</div>
|
||||
<div class="header-filters">
|
||||
<el-select
|
||||
v-model="selectedSiteId"
|
||||
clearable
|
||||
placeholder="按分销站点筛选"
|
||||
style="width: 240px"
|
||||
placeholder="全部站点(含全局)"
|
||||
style="width: 220px"
|
||||
@change="loadRules"
|
||||
>
|
||||
<el-option
|
||||
@@ -88,44 +185,75 @@ onMounted(async () => {
|
||||
:value="site.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="selectedMode"
|
||||
clearable
|
||||
placeholder="全部分销模式"
|
||||
style="width: 200px"
|
||||
@change="loadRules"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in DISTRIBUTION_MODES"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" :icon="Plus" @click="handleOpenCreate">新建规则</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="rules-alert"
|
||||
title="默认 35% 收号 / 40% 卖出 / 25% 服务费。站点专属规则优先于全局默认;通道费默认 0.5%,从主站服务费与角色分成中承担。"
|
||||
/>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-table v-loading="loading" :data="rules" stripe style="width: 100%">
|
||||
<el-table-column label="适用站点" min-width="160">
|
||||
<el-table-column label="适用站点" min-width="170">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.site_id ? 'primary' : 'info'" size="small">
|
||||
{{ getSiteName(row.site_id) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分销模式" prop="mode" min-width="140">
|
||||
<el-table-column label="分销模式" min-width="160">
|
||||
<template #default="{ row }">
|
||||
{{ modeMap[row.mode] || row.mode }}
|
||||
<div class="mode-cell">
|
||||
<strong>{{ distributionModeLabel(row.mode) }}</strong>
|
||||
<small>v{{ row.version }}</small>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="版本号" prop="version" width="80" align="center">
|
||||
<template #default="{ row }"> v{{ row.version }} </template>
|
||||
<el-table-column label="利润分成" min-width="280">
|
||||
<template #default="{ row }">
|
||||
<div class="split-cell">
|
||||
<span v-for="part in ruleSplitParts(row)" :key="part.label" class="split-chip">
|
||||
{{ part.label }} <strong>{{ part.value }}</strong>
|
||||
</span>
|
||||
<span class="split-chip service"
|
||||
>服务费 <strong>{{ formatServiceFee(row) }}</strong></span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分销商收号分成" min-width="130" align="right">
|
||||
<template #default="{ row }"> {{ row.distributor_receive_ratio }}% </template>
|
||||
<el-table-column label="通道费" min-width="140">
|
||||
<template #default="{ row }">
|
||||
<div class="fee-cell">
|
||||
<span>{{ formatPaymentFeeBps(row.payment_fee_bps) }}</span>
|
||||
<small>主站承担</small>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="平台卖号分成" min-width="120" align="right">
|
||||
<template #default="{ row }"> {{ row.platform_sell_ratio }}% </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="服务费" min-width="110" align="right">
|
||||
<template #default="{ row }"> {{ row.service_fee_value }}% </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付手续费" min-width="110" align="right">
|
||||
<template #default="{ row }"> {{ row.payment_fee_bps / 100 }}% </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="税费率" min-width="100" align="center">
|
||||
<el-table-column label="税费" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.tax_enabled" type="warning" size="small">
|
||||
{{ row.tax_rate_bps / 100 }}%
|
||||
</el-tag>
|
||||
<el-tag v-else type="info" size="small">免税</el-tag>
|
||||
<el-tag v-else type="info" size="small">关闭</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="生效时间" min-width="160">
|
||||
@@ -135,8 +263,8 @@ onMounted(async () => {
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'active' ? 'success' : 'danger'" size="small">
|
||||
{{ row.status === 'active' ? '生效中' : '已停用' }}
|
||||
<el-tag :type="statusMeta(row.status).type" size="small">
|
||||
{{ statusMeta(row.status).label }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -153,6 +281,101 @@ onMounted(async () => {
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="createVisible" title="新建分销规则" width="560px" destroy-on-close>
|
||||
<el-alert
|
||||
:closable="false"
|
||||
type="info"
|
||||
show-icon
|
||||
:title="`${createModeMeta?.label}:${createModeMeta?.hint}。分成三项合计应为 100%。`"
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
<el-form :model="createForm" label-width="120px">
|
||||
<el-form-item label="适用站点">
|
||||
<el-select
|
||||
v-model="createForm.site_id"
|
||||
clearable
|
||||
placeholder="全局默认"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="site in sites"
|
||||
:key="site.id"
|
||||
:label="`${site.site_name} (${site.site_code})`"
|
||||
:value="site.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分销模式">
|
||||
<el-select v-model="createForm.mode" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in DISTRIBUTION_MODES"
|
||||
:key="item.value"
|
||||
:label="`${item.label}(${item.hint})`"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="createForm.mode === 'three_party' ? '收号站分成' : '收号分成'">
|
||||
<el-input-number
|
||||
v-model="createForm.receive_ratio"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="0.5"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="createForm.mode === 'three_party' ? '卖出站分成' : '卖出分成'">
|
||||
<el-input-number
|
||||
v-model="createForm.sell_ratio"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="0.5"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="平台服务费">
|
||||
<el-input-number
|
||||
v-model="createForm.service_fee_value"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="0.5"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="通道费率 BPS">
|
||||
<el-input-number
|
||||
v-model="createForm.payment_fee_bps"
|
||||
:min="0"
|
||||
:max="10000"
|
||||
:step="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<div class="form-hint">
|
||||
当前 {{ formatPaymentFeeBps(createForm.payment_fee_bps) }},由主站承担,不从利润里扣。
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="征收税费">
|
||||
<el-switch v-model="createForm.tax_enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="createForm.tax_enabled" label="税费率 BPS">
|
||||
<el-input-number
|
||||
v-model="createForm.tax_rate_bps"
|
||||
:min="0"
|
||||
:max="10000"
|
||||
:step="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="立即启用">
|
||||
<el-switch v-model="createForm.activate" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="createVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="creating" @click="handleCreate">保存规则</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -165,7 +388,8 @@ onMounted(async () => {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 16px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@@ -180,4 +404,65 @@ onMounted(async () => {
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-filters {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.rules-alert {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mode-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.mode-cell small {
|
||||
color: #9ca3af;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.split-cell {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.split-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.split-chip.service {
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.fee-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.fee-cell small {
|
||||
color: #9ca3af;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
margin-top: 4px;
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,6 +12,10 @@ import {
|
||||
type DistributionSettlementItem,
|
||||
type DistributionSite,
|
||||
} from '@/features/admin/api/adminDistribution'
|
||||
import {
|
||||
distributionPartyRoleLabel,
|
||||
settlementBatchKind,
|
||||
} from '@/features/admin/utils/distributionLabels'
|
||||
import { formatCent } from '@/shared/utils/money'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
import { readError } from '@/shared/utils/error'
|
||||
@@ -33,6 +37,8 @@ const itemsLoading = ref(false)
|
||||
const currentBatch = ref<DistributionSettlementBatch | null>(null)
|
||||
const items = ref<DistributionSettlementItem[]>([])
|
||||
|
||||
const selectedSite = () => sites.value.find(site => site.id === selectedSiteId.value)
|
||||
|
||||
async function loadSites() {
|
||||
try {
|
||||
const list = await fetchDistributionSites()
|
||||
@@ -73,7 +79,7 @@ async function handleCreateBatch() {
|
||||
creating.value = true
|
||||
try {
|
||||
await createDistributionSettlementBatch(createForm.site_id, start, end)
|
||||
ElMessage.success('结算批次生成成功')
|
||||
ElMessage.success('人工结算批次已生成,可立即释放入账')
|
||||
createDialogVisible.value = false
|
||||
await loadBatches()
|
||||
} catch (err) {
|
||||
@@ -86,12 +92,12 @@ async function handleCreateBatch() {
|
||||
async function handleSettle(row: DistributionSettlementBatch) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定将结算批次 ${row.batch_no} 标记为已打款结算吗?结算总额为 ${formatCent(row.total_amount_cent)}。`,
|
||||
'确认结算',
|
||||
{ type: 'warning', confirmButtonText: '确认标记已结算', cancelButtonText: '取消' }
|
||||
`确认将批次 ${row.batch_no} 的 ${formatCent(row.total_amount_cent)} 立即转入站点可提现余额?人工批次会绕过冷却期。`,
|
||||
'立即释放入账',
|
||||
{ type: 'warning', confirmButtonText: '确认入账', cancelButtonText: '取消' }
|
||||
)
|
||||
await settleDistributionBatch(row.id)
|
||||
ElMessage.success('批次已成功标记为已结算')
|
||||
ElMessage.success('批次已释放到站点可提现余额')
|
||||
await loadBatches()
|
||||
} catch {
|
||||
// cancelled or error handled
|
||||
@@ -111,6 +117,13 @@ async function handleViewItems(row: DistributionSettlementBatch) {
|
||||
}
|
||||
}
|
||||
|
||||
function batchStatus(row: DistributionSettlementBatch) {
|
||||
if (row.status === 'settled') return { label: '已释放入账', type: 'success' as const }
|
||||
if (row.status === 'pending' || row.status === 'draft')
|
||||
return { label: '待入账', type: 'warning' as const }
|
||||
return { label: row.status, type: 'info' as const }
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSites()
|
||||
})
|
||||
@@ -120,9 +133,9 @@ onMounted(() => {
|
||||
<div class="distribution-settlements-view">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2 class="page-title">分销结算管理</h2>
|
||||
<h2 class="page-title">分销结算对账</h2>
|
||||
<p class="page-subtitle">
|
||||
按周期汇总各合作分销站点的佣金分成批次,并执行最终结算出款确认。
|
||||
冷却期满后系统按站点按天自动出对账单并入账。人工批次用于立即结算,不经过冷却期。
|
||||
</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
@@ -139,38 +152,48 @@ onMounted(() => {
|
||||
:value="site.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" :icon="Plus" @click="handleOpenCreate"> 生成结算批次 </el-button>
|
||||
<el-button type="primary" :icon="Plus" @click="handleOpenCreate"> 生成人工批次 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="settlement-alert"
|
||||
:title="`当前站点冷却期 ${selectedSite()?.settlement_hold_days ?? 7} 天。DA 开头为自动释放对账单,DS 开头为人工立即结算。`"
|
||||
/>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-table v-loading="loading" :data="batches" stripe style="width: 100%">
|
||||
<el-table-column label="结算批次号" prop="batch_no" min-width="180" />
|
||||
<el-table-column label="周期开始时间" min-width="160">
|
||||
<el-table-column label="批次号" prop="batch_no" min-width="200">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.period_start) }}
|
||||
<div class="batch-no-cell">
|
||||
<code>{{ row.batch_no }}</code>
|
||||
<el-tag :type="settlementBatchKind(row.batch_no).type" size="small">
|
||||
{{ settlementBatchKind(row.batch_no).label }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="周期结束时间" min-width="160">
|
||||
<el-table-column label="对账周期" min-width="220">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.period_end) }}
|
||||
{{ formatDateTime(row.period_start) }} ~ {{ formatDateTime(row.period_end) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应结总金额" min-width="140" align="right">
|
||||
<el-table-column label="入账金额" min-width="130" align="right">
|
||||
<template #default="{ row }">
|
||||
<strong style="color: #67c23a; font-size: 15px">
|
||||
{{ formatCent(row.total_amount_cent) }}
|
||||
</strong>
|
||||
<strong class="amount">{{ formatCent(row.total_amount_cent) }}</strong>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="批次状态" width="120" align="center">
|
||||
<el-table-column label="状态" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'settled' ? 'success' : 'warning'" size="small">
|
||||
{{ row.status === 'settled' ? '已打款' : '待结算' }}
|
||||
<el-tag :type="batchStatus(row).type" size="small">
|
||||
{{ batchStatus(row).label }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="handleViewItems(row)">查看明细</el-button>
|
||||
<el-button
|
||||
@@ -179,20 +202,26 @@ onMounted(() => {
|
||||
type="success"
|
||||
@click="handleSettle(row)"
|
||||
>
|
||||
打款结算
|
||||
立即释放入账
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 生成结算批次对话框 -->
|
||||
<el-dialog
|
||||
v-model="createDialogVisible"
|
||||
title="生成分销结算批次"
|
||||
width="500px"
|
||||
title="生成人工结算批次"
|
||||
width="520px"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-alert
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="人工批次会把周期内尚未入账的分站份额立即结算,不走冷却期。"
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="分销站点">
|
||||
<el-select v-model="createForm.site_id" style="width: 100%">
|
||||
@@ -219,29 +248,32 @@ onMounted(() => {
|
||||
<template #footer>
|
||||
<el-button @click="createDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="creating" @click="handleCreateBatch">
|
||||
立即结算汇总
|
||||
生成批次
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 结算条目明细对话框 -->
|
||||
<el-dialog
|
||||
v-model="itemsDialogVisible"
|
||||
:title="`结算批次明细 (${currentBatch?.batch_no})`"
|
||||
width="650px"
|
||||
:title="`批次明细(${currentBatch?.batch_no})`"
|
||||
width="680px"
|
||||
>
|
||||
<el-table v-loading="itemsLoading" :data="items" size="small" border>
|
||||
<el-table-column label="订单 ID" prop="order_id" width="100" />
|
||||
<el-table-column label="分成角色" prop="party_role" min-width="150" />
|
||||
<el-table-column label="应分金额" min-width="120" align="right">
|
||||
<el-table-column label="分成角色" min-width="140">
|
||||
<template #default="{ row }">
|
||||
{{ distributionPartyRoleLabel(row.party_role) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="入账金额" min-width="120" align="right">
|
||||
<template #default="{ row }">
|
||||
<strong>{{ formatCent(row.amount_cent) }}</strong>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="status" width="100" align="center">
|
||||
<el-table-column label="状态" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'settled' ? 'success' : 'info'" size="small">
|
||||
{{ row.status }}
|
||||
{{ row.status === 'settled' ? '已入账' : '待入账' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -262,7 +294,8 @@ onMounted(() => {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 16px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@@ -276,10 +309,31 @@ onMounted(() => {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.settlement-alert {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.batch-no-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.batch-no-cell code {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.amount {
|
||||
color: #16a34a;
|
||||
font-size: 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -66,6 +66,7 @@ const onboardForm = reactive({
|
||||
distributor_receive_ratio: 35,
|
||||
platform_sell_ratio: 40,
|
||||
service_fee_value: 25,
|
||||
settlement_hold_days: 7,
|
||||
display_account_enabled: true,
|
||||
tax_enabled: false,
|
||||
tax_rate_bps: 100,
|
||||
@@ -107,6 +108,7 @@ function handleOpenOnboard() {
|
||||
onboardForm.distributor_receive_ratio = 35
|
||||
onboardForm.platform_sell_ratio = 40
|
||||
onboardForm.service_fee_value = 25
|
||||
onboardForm.settlement_hold_days = 7
|
||||
onboardForm.display_account_enabled = true
|
||||
onboardForm.tax_enabled = false
|
||||
onboardForm.tax_rate_bps = 100
|
||||
@@ -130,6 +132,7 @@ async function handleOnboardSubmit() {
|
||||
distributor_receive_ratio: Number(onboardForm.distributor_receive_ratio),
|
||||
platform_sell_ratio: Number(onboardForm.platform_sell_ratio),
|
||||
service_fee_value: Number(onboardForm.service_fee_value),
|
||||
settlement_hold_days: Number(onboardForm.settlement_hold_days),
|
||||
display_account_enabled: onboardForm.display_account_enabled,
|
||||
tax_enabled: onboardForm.tax_enabled,
|
||||
tax_rate_bps: onboardForm.tax_enabled ? onboardForm.tax_rate_bps : 0,
|
||||
@@ -186,7 +189,8 @@ onMounted(() => {
|
||||
<div>
|
||||
<h2 class="page-title">分销站点管理</h2>
|
||||
<p class="page-subtitle">
|
||||
统一管理全平台合作分销站点,支持一键开通分销商、初始化管理员账号及专属佣金费率。
|
||||
开通分销站、配置结算冷却期。利润按 35% 收号 / 40% 卖出 / 25%
|
||||
服务费切分,通道费由主站承担。
|
||||
</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
@@ -321,10 +325,17 @@ onMounted(() => {
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="form-section-title">3. 佣金比例与费率初始化</div>
|
||||
<div class="form-section-title">3. 分站收号规则与冷却期</div>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="此处只生成「两方 · 分站收号」草稿。利润 = 净付款 − 号主 − 加价;通道费 0.5% 由主站承担。另外两种模式请到规则页按全局默认启用或单独配置。"
|
||||
style="margin-bottom: 12px"
|
||||
/>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="收号分成比例 (%)">
|
||||
<el-form-item label="分站收号分成 (%)">
|
||||
<el-input-number
|
||||
v-model="onboardForm.distributor_receive_ratio"
|
||||
:min="0"
|
||||
@@ -335,7 +346,7 @@ onMounted(() => {
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="平台卖号分成 (%)">
|
||||
<el-form-item label="主站卖出分成 (%)">
|
||||
<el-input-number
|
||||
v-model="onboardForm.platform_sell_ratio"
|
||||
:min="0"
|
||||
@@ -358,15 +369,30 @@ onMounted(() => {
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结算冷却期 (天)">
|
||||
<el-input-number
|
||||
v-model="onboardForm.settlement_hold_days"
|
||||
:min="0"
|
||||
:max="365"
|
||||
:step="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="展示主站账号">
|
||||
<el-switch v-model="onboardForm.display_account_enabled" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否征税">
|
||||
<el-switch v-model="onboardForm.tax_enabled" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="是否征税">
|
||||
<el-switch v-model="onboardForm.tax_enabled" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onboardDialogVisible = false">取消</el-button>
|
||||
@@ -400,9 +426,12 @@ onMounted(() => {
|
||||
<code>http://{{ windowHost }}:5190/login</code>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="初始规则">
|
||||
收号分成: {{ onboardResult.rule.distributor_receive_ratio }}% · 卖号分成:
|
||||
{{ onboardResult.rule.platform_sell_ratio }}% · 服务费:
|
||||
{{ onboardResult.rule.service_fee_value }}%
|
||||
分站收号 {{ onboardResult.rule.distributor_receive_ratio }}% · 主站卖出
|
||||
{{ onboardResult.rule.platform_sell_ratio }}% · 服务费
|
||||
{{ onboardResult.rule.service_fee_value }}%(草稿,通道费主站承担)
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="结算冷却期">
|
||||
{{ onboardResult.site.settlement_hold_days }} 天
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
@@ -158,7 +158,7 @@ const allNavGroups: NavGroup[] = [
|
||||
permission: 'distribution:order_view',
|
||||
},
|
||||
{
|
||||
label: '分销结算',
|
||||
label: '结算对账',
|
||||
to: adminPath('distribution/settlements'),
|
||||
icon: Coin,
|
||||
permission: 'distribution:settlement_view',
|
||||
|
||||
Reference in New Issue
Block a user