feat(distribution): 独立分销后台账号体系、多租户隔离与主站后台分销管理GUI
- 独立账号鉴权:分销后台支持独立登录页面、Bearer+Cookie 双模鉴权与租户资料接口 - 多租户隔离:分销商成员强制锁定自身站点数据范围,防止横向越权;平台超管享有全局视界 - 一键开通分销商:平台端支持一键录入站点、管理员账号、分配角色并原子化初始化佣金规则 - 主后台分销分组:在主站后台增加「分销管理」导航分组,提供站点管理、规则配置、订单监控与结算管理 GUI - 门禁与联调:补充完整单测,全量代码门禁 check.sh 100% 通过,dev.sh 支持前后端与分销后台一键同启
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
import { apiClient } from '@/shared/api/client'
|
||||
import type { ApiResponse } from '@/shared/types/types'
|
||||
|
||||
export interface DistributionSite {
|
||||
id: number
|
||||
site_code: string
|
||||
site_name: string
|
||||
domain: string
|
||||
status: string
|
||||
display_account_enabled: boolean
|
||||
tax_enabled: boolean
|
||||
tax_rate_bps: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface DistributionRule {
|
||||
id: number
|
||||
site_id?: number
|
||||
mode: string
|
||||
version: number
|
||||
status: string
|
||||
platform_receive_ratio: number
|
||||
platform_sell_ratio: number
|
||||
distributor_receive_ratio: number
|
||||
distributor_sell_ratio: number
|
||||
distributor_a_ratio: number
|
||||
distributor_b_ratio: number
|
||||
service_fee_value: number
|
||||
service_fee_unit: string
|
||||
payment_fee_bps: number
|
||||
tax_enabled: boolean
|
||||
tax_rate_bps: number
|
||||
effective_from: string
|
||||
effective_to?: string
|
||||
}
|
||||
|
||||
export interface DistributionOrder {
|
||||
order_id: number
|
||||
order_no: string
|
||||
site_id: number
|
||||
status: string
|
||||
refund_status: string
|
||||
settlement_status: string
|
||||
distribution_mode: string
|
||||
net_paid_amount_cent: number
|
||||
payment_fee_cent: number
|
||||
service_fee_cent: number
|
||||
markup_amount_cent: number
|
||||
tax_amount_cent: number
|
||||
split_base_amount_cent: number
|
||||
rounding_adjustment_cent: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface DistributionOrderPage {
|
||||
items: DistributionOrder[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
export interface DistributionSettlementBatch {
|
||||
id: number
|
||||
site_id: number
|
||||
batch_no: string
|
||||
period_start: string
|
||||
period_end: string
|
||||
total_amount_cent: number
|
||||
status: string
|
||||
}
|
||||
|
||||
export interface DistributionSettlementItem {
|
||||
id: number
|
||||
order_id: number
|
||||
party_role: string
|
||||
amount_cent: number
|
||||
status: string
|
||||
}
|
||||
|
||||
export interface OnboardDistributionSitePayload {
|
||||
site_code: string
|
||||
site_name: string
|
||||
domain: string
|
||||
admin_username: string
|
||||
admin_password: string
|
||||
admin_nickname?: string
|
||||
member_type?: string
|
||||
display_account_enabled?: boolean
|
||||
tax_enabled?: boolean
|
||||
tax_rate_bps?: number
|
||||
distributor_receive_ratio?: number
|
||||
platform_sell_ratio?: number
|
||||
service_fee_value?: number
|
||||
}
|
||||
|
||||
export interface OnboardDistributionSiteResult {
|
||||
site: DistributionSite
|
||||
rule: DistributionRule
|
||||
member: {
|
||||
admin_id: number
|
||||
username: string
|
||||
nickname: string
|
||||
is_superadmin: boolean
|
||||
site_id?: number
|
||||
site_code?: string
|
||||
site_name?: string
|
||||
domain?: string
|
||||
member_type?: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchDistributionSites() {
|
||||
const { data } = await apiClient.get<ApiResponse<{ items: DistributionSite[] }>>(
|
||||
'/admin/distribution/sites'
|
||||
)
|
||||
return data.data.items ?? []
|
||||
}
|
||||
|
||||
export async function onboardDistributionSite(payload: OnboardDistributionSitePayload) {
|
||||
const { data } = await apiClient.post<ApiResponse<OnboardDistributionSiteResult>>(
|
||||
'/admin/distribution/sites/onboard',
|
||||
payload
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function updateDistributionSite(
|
||||
id: number,
|
||||
payload: Partial<
|
||||
Pick<
|
||||
DistributionSite,
|
||||
'site_name' | 'domain' | 'status' | 'display_account_enabled' | 'tax_enabled' | 'tax_rate_bps'
|
||||
>
|
||||
>
|
||||
) {
|
||||
const { data } = await apiClient.put<ApiResponse<DistributionSite>>(
|
||||
`/admin/distribution/sites/${id}`,
|
||||
payload
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchDistributionRules(siteId?: number, mode?: string) {
|
||||
const { data } = await apiClient.get<ApiResponse<{ items: DistributionRule[] }>>(
|
||||
'/admin/distribution/rules',
|
||||
{
|
||||
params: { site_id: siteId || undefined, mode: mode || undefined },
|
||||
}
|
||||
)
|
||||
return data.data.items ?? []
|
||||
}
|
||||
|
||||
export interface CreateRulePayload {
|
||||
site_id?: number
|
||||
mode: string
|
||||
distributor_receive_ratio?: number
|
||||
platform_sell_ratio?: number
|
||||
distributor_sell_ratio?: number
|
||||
platform_receive_ratio?: number
|
||||
distributor_a_ratio?: number
|
||||
distributor_b_ratio?: number
|
||||
service_fee_value: number
|
||||
service_fee_unit: string
|
||||
payment_fee_bps: number
|
||||
tax_enabled: boolean
|
||||
tax_rate_bps: number
|
||||
effective_from?: string
|
||||
}
|
||||
|
||||
export async function createDistributionRule(payload: CreateRulePayload) {
|
||||
const { data } = await apiClient.post<ApiResponse<DistributionRule>>(
|
||||
'/admin/distribution/rules',
|
||||
payload
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function updateDistributionRuleStatus(id: number, status: string) {
|
||||
const { data } = await apiClient.put<ApiResponse<DistributionRule>>(
|
||||
`/admin/distribution/rules/${id}/status`,
|
||||
{ status }
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchDistributionOrders(params: {
|
||||
kind?: string
|
||||
status?: string
|
||||
siteID?: number
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}) {
|
||||
const { data } = await apiClient.get<ApiResponse<DistributionOrderPage>>(
|
||||
'/admin/distribution/orders',
|
||||
{
|
||||
params: {
|
||||
kind: params.kind || undefined,
|
||||
status: params.status || undefined,
|
||||
site_id: params.siteID || undefined,
|
||||
page: params.page || 1,
|
||||
page_size: params.pageSize || 20,
|
||||
},
|
||||
}
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchDistributionSettlementBatches(siteId: number) {
|
||||
const { data } = await apiClient.get<ApiResponse<{ items: DistributionSettlementBatch[] }>>(
|
||||
'/admin/distribution/settlement-batches',
|
||||
{
|
||||
params: { site_id: siteId },
|
||||
}
|
||||
)
|
||||
return data.data.items ?? []
|
||||
}
|
||||
|
||||
export async function createDistributionSettlementBatch(
|
||||
siteId: number,
|
||||
periodStart: string,
|
||||
periodEnd: string
|
||||
) {
|
||||
const { data } = await apiClient.post<ApiResponse<DistributionSettlementBatch>>(
|
||||
'/admin/distribution/settlement-batches',
|
||||
{
|
||||
site_id: siteId,
|
||||
period_start: periodStart,
|
||||
period_end: periodEnd,
|
||||
}
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function settleDistributionBatch(id: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<DistributionSettlementBatch>>(
|
||||
`/admin/distribution/settlement-batches/${id}/settle`
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchDistributionSettlementItems(batchId: number) {
|
||||
const { data } = await apiClient.get<ApiResponse<{ items: DistributionSettlementItem[] }>>(
|
||||
`/admin/distribution/settlement-batches/${batchId}/items`
|
||||
)
|
||||
return data.data.items ?? []
|
||||
}
|
||||
@@ -7,6 +7,7 @@ export * from './api/adminPayments'
|
||||
export * from './api/adminFinance'
|
||||
export * from './api/adminAudit'
|
||||
export * from './api/adminNotifications'
|
||||
export * from './api/adminDistribution'
|
||||
export * from './api/systemConfigs'
|
||||
export * from './api/supportGroups'
|
||||
export * from './composables/useAdminTable'
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
fetchDistributionOrders,
|
||||
fetchDistributionSites,
|
||||
type DistributionOrder,
|
||||
type DistributionSite,
|
||||
} from '@/features/admin/api/adminDistribution'
|
||||
import { formatCent } from '@/shared/utils/money'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
import { readError } from '@/shared/utils/error'
|
||||
|
||||
const loading = ref(false)
|
||||
const sites = ref<DistributionSite[]>([])
|
||||
const orders = ref<DistributionOrder[]>([])
|
||||
const total = ref(0)
|
||||
|
||||
const query = reactive({
|
||||
siteID: undefined as number | undefined,
|
||||
kind: '' as string,
|
||||
status: '' as string,
|
||||
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()
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '获取分销站点失败'))
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOrders() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchDistributionOrders({
|
||||
siteID: query.siteID,
|
||||
kind: query.kind || undefined,
|
||||
status: query.status || undefined,
|
||||
page: query.page,
|
||||
pageSize: query.pageSize,
|
||||
})
|
||||
orders.value = res.items ?? []
|
||||
total.value = res.total ?? 0
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '获取分销订单失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
query.page = 1
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
query.siteID = undefined
|
||||
query.kind = ''
|
||||
query.status = ''
|
||||
query.page = 1
|
||||
loadOrders()
|
||||
}
|
||||
|
||||
function getSiteName(siteId: number) {
|
||||
const site = sites.value.find(s => s.id === siteId)
|
||||
return site ? `${site.site_name}` : `站点 #${siteId}`
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadSites()
|
||||
await loadOrders()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="distribution-orders-view">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2 class="page-title">分销订单监控</h2>
|
||||
<p class="page-subtitle">
|
||||
全览各合作分销站点的收号与售出订单明细、渠道手续费、单笔服务费及利润分成流水。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card shadow="never" class="filter-card">
|
||||
<el-form :inline="true" :model="query">
|
||||
<el-form-item label="分销站点">
|
||||
<el-select v-model="query.siteID" clearable placeholder="全部分销站" style="width: 180px">
|
||||
<el-option
|
||||
v-for="site in sites"
|
||||
:key="site.id"
|
||||
:label="site.site_name"
|
||||
:value="site.id"
|
||||
/>
|
||||
</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-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-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<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">
|
||||
<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">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info" size="small">
|
||||
{{ modeMap[row.distribution_mode] || row.distribution_mode }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单实付" min-width="110" 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">
|
||||
<template #default="{ row }">
|
||||
{{ formatCent(row.payment_fee_cent) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="固定服务费" min-width="100" 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">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'completed' ? 'success' : 'warning'" size="small">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.settlement_status === 'settled' ? 'success' : 'info'" size="small">
|
||||
{{ row.settlement_status === 'settled' ? '已结算' : '待结算' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" min-width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-bar">
|
||||
<el-pagination
|
||||
v-model:current-page="query.page"
|
||||
v-model:page-size="query.pageSize"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="loadOrders"
|
||||
@size-change="loadOrders"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.distribution-orders-view {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 6px 0;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.pagination-bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,183 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
fetchDistributionRules,
|
||||
fetchDistributionSites,
|
||||
updateDistributionRuleStatus,
|
||||
type DistributionRule,
|
||||
type DistributionSite,
|
||||
} from '@/features/admin/api/adminDistribution'
|
||||
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 rules = ref<DistributionRule[]>([])
|
||||
|
||||
const modeMap: Record<string, string> = {
|
||||
two_party_distributor_receive: '两方-分销收号',
|
||||
two_party_platform_receive: '两方-分销卖出',
|
||||
three_party: '三方分销',
|
||||
}
|
||||
|
||||
async function loadSites() {
|
||||
try {
|
||||
sites.value = await fetchDistributionSites()
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '获取分销站点失败'))
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRules() {
|
||||
loading.value = true
|
||||
try {
|
||||
rules.value = await fetchDistributionRules(selectedSiteId.value)
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '获取分销规则列表失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleToggleStatus(row: DistributionRule) {
|
||||
const next = row.status === 'active' ? 'disabled' : 'active'
|
||||
try {
|
||||
await updateDistributionRuleStatus(row.id, next)
|
||||
ElMessage.success(`规则已${next === 'active' ? '启用' : '停用'}`)
|
||||
await loadRules()
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '更新规则状态失败'))
|
||||
}
|
||||
}
|
||||
|
||||
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}`
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadSites()
|
||||
await loadRules()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="distribution-rules-view">
|
||||
<div class="page-header">
|
||||
<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"
|
||||
@change="loadRules"
|
||||
>
|
||||
<el-option
|
||||
v-for="site in sites"
|
||||
:key="site.id"
|
||||
:label="`${site.site_name} (${site.site_code})`"
|
||||
:value="site.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-table v-loading="loading" :data="rules" stripe style="width: 100%">
|
||||
<el-table-column label="适用站点" min-width="160">
|
||||
<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">
|
||||
<template #default="{ row }">
|
||||
{{ modeMap[row.mode] || row.mode }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="版本号" prop="version" width="80" align="center">
|
||||
<template #default="{ row }"> v{{ row.version }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分销商收号分成" min-width="130" align="right">
|
||||
<template #default="{ row }"> {{ row.distributor_receive_ratio }}% </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">
|
||||
<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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="生效时间" min-width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.effective_from) }}
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
:type="row.status === 'active' ? 'danger' : 'success'"
|
||||
@click="handleToggleStatus(row)"
|
||||
>
|
||||
{{ row.status === 'active' ? '停用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.distribution-rules-view {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 6px 0;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,285 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import {
|
||||
fetchDistributionSettlementBatches,
|
||||
fetchDistributionSettlementItems,
|
||||
createDistributionSettlementBatch,
|
||||
settleDistributionBatch,
|
||||
fetchDistributionSites,
|
||||
type DistributionSettlementBatch,
|
||||
type DistributionSettlementItem,
|
||||
type DistributionSite,
|
||||
} from '@/features/admin/api/adminDistribution'
|
||||
import { formatCent } from '@/shared/utils/money'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
import { readError } from '@/shared/utils/error'
|
||||
|
||||
const loading = ref(false)
|
||||
const sites = ref<DistributionSite[]>([])
|
||||
const selectedSiteId = ref<number>()
|
||||
const batches = ref<DistributionSettlementBatch[]>([])
|
||||
|
||||
const createDialogVisible = ref(false)
|
||||
const creating = ref(false)
|
||||
const createForm = reactive({
|
||||
site_id: undefined as number | undefined,
|
||||
date_range: [] as string[],
|
||||
})
|
||||
|
||||
const itemsDialogVisible = ref(false)
|
||||
const itemsLoading = ref(false)
|
||||
const currentBatch = ref<DistributionSettlementBatch | null>(null)
|
||||
const items = ref<DistributionSettlementItem[]>([])
|
||||
|
||||
async function loadSites() {
|
||||
try {
|
||||
const list = await fetchDistributionSites()
|
||||
sites.value = list
|
||||
if (list.length > 0 && list[0]) {
|
||||
selectedSiteId.value = list[0].id
|
||||
await loadBatches()
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '获取分销站点失败'))
|
||||
}
|
||||
}
|
||||
|
||||
async function loadBatches() {
|
||||
if (!selectedSiteId.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
batches.value = await fetchDistributionSettlementBatches(selectedSiteId.value)
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '获取结算批次失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleOpenCreate() {
|
||||
createForm.site_id = selectedSiteId.value
|
||||
createForm.date_range = []
|
||||
createDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleCreateBatch() {
|
||||
const [start, end] = createForm.date_range
|
||||
if (!createForm.site_id || !start || !end) {
|
||||
ElMessage.warning('请选择站点与结算周期起止时间')
|
||||
return
|
||||
}
|
||||
creating.value = true
|
||||
try {
|
||||
await createDistributionSettlementBatch(createForm.site_id, start, end)
|
||||
ElMessage.success('结算批次生成成功')
|
||||
createDialogVisible.value = false
|
||||
await loadBatches()
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '生成结算批次失败'))
|
||||
} finally {
|
||||
creating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSettle(row: DistributionSettlementBatch) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定将结算批次 ${row.batch_no} 标记为已打款结算吗?结算总额为 ${formatCent(row.total_amount_cent)}。`,
|
||||
'确认结算',
|
||||
{ type: 'warning', confirmButtonText: '确认标记已结算', cancelButtonText: '取消' }
|
||||
)
|
||||
await settleDistributionBatch(row.id)
|
||||
ElMessage.success('批次已成功标记为已结算')
|
||||
await loadBatches()
|
||||
} catch {
|
||||
// cancelled or error handled
|
||||
}
|
||||
}
|
||||
|
||||
async function handleViewItems(row: DistributionSettlementBatch) {
|
||||
currentBatch.value = row
|
||||
itemsDialogVisible.value = true
|
||||
itemsLoading.value = true
|
||||
try {
|
||||
items.value = await fetchDistributionSettlementItems(row.id)
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '获取结算明细失败'))
|
||||
} finally {
|
||||
itemsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSites()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="distribution-settlements-view">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2 class="page-title">分销结算管理</h2>
|
||||
<p class="page-subtitle">
|
||||
按周期汇总各合作分销站点的佣金分成批次,并执行最终结算出款确认。
|
||||
</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-select
|
||||
v-model="selectedSiteId"
|
||||
placeholder="选择分销站点"
|
||||
style="width: 220px"
|
||||
@change="loadBatches"
|
||||
>
|
||||
<el-option
|
||||
v-for="site in sites"
|
||||
:key="site.id"
|
||||
:label="`${site.site_name} (${site.site_code})`"
|
||||
:value="site.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" :icon="Plus" @click="handleOpenCreate"> 生成结算批次 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.period_start) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="周期结束时间" min-width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.period_end) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应结总金额" min-width="140" align="right">
|
||||
<template #default="{ row }">
|
||||
<strong style="color: #67c23a; font-size: 15px">
|
||||
{{ formatCent(row.total_amount_cent) }}
|
||||
</strong>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="handleViewItems(row)">查看明细</el-button>
|
||||
<el-button
|
||||
v-if="row.status !== 'settled'"
|
||||
link
|
||||
type="success"
|
||||
@click="handleSettle(row)"
|
||||
>
|
||||
打款结算
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 生成结算批次对话框 -->
|
||||
<el-dialog
|
||||
v-model="createDialogVisible"
|
||||
title="生成分销结算批次"
|
||||
width="500px"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="分销站点">
|
||||
<el-select v-model="createForm.site_id" 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-date-picker
|
||||
v-model="createForm.date_range"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<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"
|
||||
>
|
||||
<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">
|
||||
<template #default="{ row }">
|
||||
<strong>{{ formatCent(row.amount_cent) }}</strong>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="status" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'settled' ? 'success' : 'info'" size="small">
|
||||
{{ row.status }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="itemsDialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.distribution-settlements-view {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 6px 0;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,406 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { Plus, Shop, Link, DocumentCopy } from '@element-plus/icons-vue'
|
||||
import {
|
||||
fetchDistributionSites,
|
||||
onboardDistributionSite,
|
||||
type DistributionSite,
|
||||
type OnboardDistributionSitePayload,
|
||||
type OnboardDistributionSiteResult,
|
||||
} from '@/features/admin/api/adminDistribution'
|
||||
import { formatDateTime } from '@/shared/utils/time'
|
||||
import { readError } from '@/shared/utils/error'
|
||||
|
||||
const loading = ref(false)
|
||||
const sites = ref<DistributionSite[]>([])
|
||||
|
||||
const onboardDialogVisible = ref(false)
|
||||
const submitting = ref(false)
|
||||
const onboardFormRef = ref<FormInstance>()
|
||||
|
||||
const successDialogVisible = ref(false)
|
||||
const onboardResult = ref<OnboardDistributionSiteResult | null>(null)
|
||||
|
||||
const onboardForm = reactive({
|
||||
site_name: '',
|
||||
site_code: '',
|
||||
domain: '',
|
||||
admin_username: '',
|
||||
admin_password: '',
|
||||
admin_nickname: '',
|
||||
member_type: 'operator',
|
||||
distributor_receive_ratio: 3.5,
|
||||
platform_sell_ratio: 4.0,
|
||||
service_fee_value: 2.5,
|
||||
display_account_enabled: true,
|
||||
tax_enabled: false,
|
||||
tax_rate_bps: 100,
|
||||
})
|
||||
|
||||
const onboardRules: FormRules = {
|
||||
site_name: [{ required: true, message: '请输入分销站点名称', trigger: 'blur' }],
|
||||
site_code: [
|
||||
{ required: true, message: '请输入站点唯一标识', trigger: 'blur' },
|
||||
{ pattern: /^[a-zA-Z0-9_-]+$/, message: '仅支持英文字母、数字及下划线', trigger: 'blur' },
|
||||
],
|
||||
domain: [{ required: true, message: '请输入分销专属域名', trigger: 'blur' }],
|
||||
admin_username: [{ required: true, message: '请输入分销管理员账号', trigger: 'blur' }],
|
||||
admin_password: [
|
||||
{ required: true, message: '请输入初始密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码长度不能少于6位', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
async function loadSites() {
|
||||
loading.value = true
|
||||
try {
|
||||
sites.value = await fetchDistributionSites()
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '获取分销站点列表失败'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleOpenOnboard() {
|
||||
onboardForm.site_name = ''
|
||||
onboardForm.site_code = ''
|
||||
onboardForm.domain = ''
|
||||
onboardForm.admin_username = ''
|
||||
onboardForm.admin_password = ''
|
||||
onboardForm.admin_nickname = ''
|
||||
onboardForm.member_type = 'operator'
|
||||
onboardForm.distributor_receive_ratio = 3.5
|
||||
onboardForm.platform_sell_ratio = 4.0
|
||||
onboardForm.service_fee_value = 2.5
|
||||
onboardForm.display_account_enabled = true
|
||||
onboardForm.tax_enabled = false
|
||||
onboardForm.tax_rate_bps = 100
|
||||
onboardDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleOnboardSubmit() {
|
||||
if (!onboardFormRef.value) return
|
||||
await onboardFormRef.value.validate(async valid => {
|
||||
if (!valid) return
|
||||
submitting.value = true
|
||||
try {
|
||||
const payload: OnboardDistributionSitePayload = {
|
||||
site_name: onboardForm.site_name.trim(),
|
||||
site_code: onboardForm.site_code.trim(),
|
||||
domain: onboardForm.domain.trim(),
|
||||
admin_username: onboardForm.admin_username.trim(),
|
||||
admin_password: onboardForm.admin_password,
|
||||
admin_nickname: onboardForm.admin_nickname.trim() || undefined,
|
||||
member_type: onboardForm.member_type,
|
||||
distributor_receive_ratio: Number(onboardForm.distributor_receive_ratio),
|
||||
platform_sell_ratio: Number(onboardForm.platform_sell_ratio),
|
||||
service_fee_value: Number(onboardForm.service_fee_value),
|
||||
display_account_enabled: onboardForm.display_account_enabled,
|
||||
tax_enabled: onboardForm.tax_enabled,
|
||||
tax_rate_bps: onboardForm.tax_enabled ? onboardForm.tax_rate_bps : 0,
|
||||
}
|
||||
const res = await onboardDistributionSite(payload)
|
||||
ElMessage.success('分销站点与管理员账号已成功开通!')
|
||||
onboardDialogVisible.value = false
|
||||
onboardResult.value = res
|
||||
successDialogVisible.value = true
|
||||
await loadSites()
|
||||
} catch (err) {
|
||||
ElMessage.error(readError(err, '开通分销商失败'))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleCopyInfo() {
|
||||
if (!onboardResult.value) return
|
||||
const text = `分销站点:${onboardResult.value.site.site_name} (${onboardResult.value.site.site_code})\n分销域名:${onboardResult.value.site.domain}\n后台登录地址:http://${window.location.hostname}:5190/login\n管理员账号:${onboardResult.value.member.username}`
|
||||
navigator.clipboard?.writeText(text)
|
||||
ElMessage.success('已复制分销商开通凭证到剪贴板')
|
||||
}
|
||||
|
||||
function openDistributionAdmin() {
|
||||
window.open(`http://${window.location.hostname}:5190`, '_blank')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSites()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="distribution-sites-view">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2 class="page-title">分销站点管理</h2>
|
||||
<p class="page-subtitle">
|
||||
统一管理全平台合作分销站点,支持一键开通分销商、初始化管理员账号及专属佣金费率。
|
||||
</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-button :icon="Link" @click="openDistributionAdmin"> 打开独立分销后台 </el-button>
|
||||
<el-button type="primary" :icon="Plus" @click="handleOpenOnboard">
|
||||
一键开通分销商
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-table v-loading="loading" :data="sites" stripe style="width: 100%">
|
||||
<el-table-column label="站点名称" prop="site_name" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<div class="site-name-cell">
|
||||
<el-icon class="site-icon"><Shop /></el-icon>
|
||||
<strong>{{ row.site_name }}</strong>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="站点标识" prop="site_code" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small">{{ row.site_code }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="绑定域名" prop="domain" min-width="180" />
|
||||
<el-table-column label="运行状态" prop="status" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'active' ? 'success' : 'info'" size="small">
|
||||
{{ row.status === 'active' ? '正常' : '已停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="展示主站账号" prop="display_account_enabled" width="130">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.display_account_enabled ? 'success' : 'info'" size="small">
|
||||
{{ row.display_account_enabled ? '已开启' : '关闭' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="税费设定" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.tax_enabled" type="warning" size="small">
|
||||
{{ (row.tax_rate_bps / 100).toFixed(1) }}%
|
||||
</el-tag>
|
||||
<el-tag v-else type="info" size="small">免税</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="created_at" min-width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 一键开通分销商对话框 -->
|
||||
<el-dialog v-model="onboardDialogVisible" title="一键开通分销商" width="680px" destroy-on-close>
|
||||
<el-form ref="onboardFormRef" :model="onboardForm" :rules="onboardRules" label-width="140px">
|
||||
<div class="form-section-title">1. 分销站点基础信息</div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="站点名称" prop="site_name">
|
||||
<el-input v-model="onboardForm.site_name" placeholder="例如:广州游戏分销站" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="站点唯一标识" prop="site_code">
|
||||
<el-input v-model="onboardForm.site_code" placeholder="例如:gz_dist" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="分销专属域名" prop="domain">
|
||||
<el-input v-model="onboardForm.domain" placeholder="例如:gz.distribution.hao858.com" />
|
||||
</el-form-item>
|
||||
|
||||
<div class="form-section-title">2. 分销商管理员账号</div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="登录用户名" prop="admin_username">
|
||||
<el-input v-model="onboardForm.admin_username" placeholder="例如:gz_admin" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="初始登录密码" prop="admin_password">
|
||||
<el-input
|
||||
v-model="onboardForm.admin_password"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="至少6位"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="站长姓名/昵称" prop="admin_nickname">
|
||||
<el-input v-model="onboardForm.admin_nickname" placeholder="例如:张站长" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="初始角色权限" prop="member_type">
|
||||
<el-select v-model="onboardForm.member_type" style="width: 100%">
|
||||
<el-option label="分销运营 (operator)" value="operator" />
|
||||
<el-option label="分销财务 (finance)" value="finance" />
|
||||
<el-option label="客服人员 (customer_service)" value="customer_service" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="form-section-title">3. 佣金比例与费率初始化</div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="收号分成比例 (%)">
|
||||
<el-input-number
|
||||
v-model="onboardForm.distributor_receive_ratio"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="0.1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="平台卖号分成 (%)">
|
||||
<el-input-number
|
||||
v-model="onboardForm.platform_sell_ratio"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="0.1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单笔服务费 (元)">
|
||||
<el-input-number
|
||||
v-model="onboardForm.service_fee_value"
|
||||
:min="0"
|
||||
:step="0.5"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="展示主站账号">
|
||||
<el-switch v-model="onboardForm.display_account_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>
|
||||
<el-button type="primary" :loading="submitting" @click="handleOnboardSubmit">
|
||||
立即开通并生成分销商
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 开通成功凭证对话框 -->
|
||||
<el-dialog v-model="successDialogVisible" title="分销商开通成功" width="560px">
|
||||
<div v-if="onboardResult">
|
||||
<el-alert
|
||||
type="success"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="分销站点与管理员账号已成功初始化,请妥善保管交付凭证:"
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
<el-descriptions :column="1" border size="small">
|
||||
<el-descriptions-item label="分销站点">
|
||||
<strong>{{ onboardResult.site.site_name }}</strong> ({{ onboardResult.site.site_code }})
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="分销专属域名">
|
||||
{{ onboardResult.site.domain }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="分销后台登录账号">
|
||||
<el-tag type="success">{{ onboardResult.member.username }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="分销后台地址">
|
||||
<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
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button :icon="DocumentCopy" type="primary" @click="handleCopyInfo">
|
||||
一键复制交付信息
|
||||
</el-button>
|
||||
<el-button @click="successDialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
windowHost: typeof window !== 'undefined' ? window.location.hostname : 'localhost',
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.distribution-sites-view {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 6px 0;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.site-name-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.site-icon {
|
||||
color: #409eff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.form-section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin: 16px 0 12px 0;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px dashed #e5e7eb;
|
||||
}
|
||||
</style>
|
||||
@@ -164,7 +164,6 @@ function userText(value: string | number | undefined) {
|
||||
function isPlatformManaged(row: AdminOrderListItem) {
|
||||
return row.handoff_mode === 'platform' || row.settlement_mode === 'platform_managed'
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
Reference in New Issue
Block a user