437 lines
15 KiB
Vue
437 lines
15 KiB
Vue
<script setup lang="ts">
|
||
import { ref, reactive, onMounted } from 'vue'
|
||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||
import { Plus, Shop, Link, DocumentCopy, Refresh } from '@element-plus/icons-vue'
|
||
import {
|
||
fetchDistributionSites,
|
||
onboardDistributionSite,
|
||
syncDistributionSiteAccounts,
|
||
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')
|
||
}
|
||
|
||
const syncingSiteId = ref<number | null>(null)
|
||
|
||
async function handleSyncAccounts(row: DistributionSite) {
|
||
syncingSiteId.value = row.id
|
||
try {
|
||
const res = await syncDistributionSiteAccounts(row.id)
|
||
ElMessage.success(
|
||
`同步成功!为【${row.site_name}】新增可售账号 ${res.synced_count} 个,已存在 ${res.skipped_count} 个(主站当前可上架总数 ${res.total_active})`
|
||
)
|
||
} catch (err) {
|
||
ElMessage.error(readError(err, '同步商品失败'))
|
||
} finally {
|
||
syncingSiteId.value = null
|
||
}
|
||
}
|
||
|
||
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-column label="操作" width="140" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button
|
||
type="primary"
|
||
link
|
||
:icon="Refresh"
|
||
:loading="syncingSiteId === row.id"
|
||
@click="handleSyncAccounts(row)"
|
||
>
|
||
同步商品
|
||
</el-button>
|
||
</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>
|