feat(distribution): 独立分销后台账号体系、多租户隔离与主站后台分销管理GUI
- 独立账号鉴权:分销后台支持独立登录页面、Bearer+Cookie 双模鉴权与租户资料接口 - 多租户隔离:分销商成员强制锁定自身站点数据范围,防止横向越权;平台超管享有全局视界 - 一键开通分销商:平台端支持一键录入站点、管理员账号、分配角色并原子化初始化佣金规则 - 主后台分销分组:在主站后台增加「分销管理」导航分组,提供站点管理、规则配置、订单监控与结算管理 GUI - 门禁与联调:补充完整单测,全量代码门禁 check.sh 100% 通过,dev.sh 支持前后端与分销后台一键同启
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user