1381 lines
47 KiB
Vue
1381 lines
47 KiB
Vue
<script setup lang="ts">
|
||
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
|
||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||
import {
|
||
Plus,
|
||
Shop,
|
||
Link,
|
||
DocumentCopy,
|
||
Refresh,
|
||
Clock,
|
||
Coin,
|
||
Message,
|
||
Edit,
|
||
Delete,
|
||
Search,
|
||
Key,
|
||
ArrowDown,
|
||
} from '@element-plus/icons-vue'
|
||
import {
|
||
fetchDistributionSites,
|
||
fetchDistributionSiteDefaults,
|
||
onboardDistributionSite,
|
||
syncDistributionSiteAccounts,
|
||
updateDistributionSite,
|
||
deleteDistributionSite,
|
||
resetDistributionSiteAdminPassword,
|
||
fetchDistributionSiteSMSConfig,
|
||
updateDistributionSiteSMSConfig,
|
||
testDistributionSiteSMSConfig,
|
||
type DistributionSite,
|
||
type OnboardDistributionSitePayload,
|
||
type OnboardDistributionSiteResult,
|
||
} from '@/features/admin/api/adminDistribution'
|
||
import { formatDateTime } from '@/shared/utils/time'
|
||
import { readError } from '@/shared/utils/error'
|
||
import { smsNodeMetas } from '@/features/admin/api/smsNodeTemplates'
|
||
import {
|
||
distributionAdminBaseUrl,
|
||
distributionAdminLoginUrl,
|
||
} from '@/shared/utils/distributionAdmin'
|
||
|
||
const loading = ref(false)
|
||
const sites = ref<DistributionSite[]>([])
|
||
const defaultSettlementHoldDays = ref(7)
|
||
|
||
// 筛选检索状态
|
||
const searchQuery = ref('')
|
||
const statusFilter = ref('')
|
||
const displayAccountFilter = ref('')
|
||
|
||
const filteredSites = computed(() => {
|
||
return sites.value.filter(site => {
|
||
if (statusFilter.value && site.status !== statusFilter.value) {
|
||
return false
|
||
}
|
||
if (displayAccountFilter.value !== '') {
|
||
const want = displayAccountFilter.value === 'true'
|
||
if (site.display_account_enabled !== want) return false
|
||
}
|
||
if (searchQuery.value.trim()) {
|
||
const q = searchQuery.value.trim().toLowerCase()
|
||
const matchName = site.site_name?.toLowerCase().includes(q)
|
||
const matchCode = site.site_code?.toLowerCase().includes(q)
|
||
const matchDomain = site.domain?.toLowerCase().includes(q)
|
||
const matchAdmin =
|
||
site.admin_username?.toLowerCase().includes(q) ||
|
||
site.admin_nickname?.toLowerCase().includes(q)
|
||
if (!matchName && !matchCode && !matchDomain && !matchAdmin) {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
})
|
||
})
|
||
|
||
function handleResetFilter() {
|
||
searchQuery.value = ''
|
||
statusFilter.value = ''
|
||
displayAccountFilter.value = ''
|
||
}
|
||
|
||
function copyDomain(domain: string) {
|
||
navigator.clipboard?.writeText(domain)
|
||
ElMessage.success('域名已复制到剪贴板')
|
||
}
|
||
|
||
const onboardDialogVisible = ref(false)
|
||
const submitting = ref(false)
|
||
const onboardFormRef = ref<FormInstance>()
|
||
|
||
const successDialogVisible = ref(false)
|
||
const onboardResult = ref<OnboardDistributionSiteResult | null>(null)
|
||
|
||
const settlementDialogVisible = ref(false)
|
||
const settlementSubmitting = ref(false)
|
||
const settlementForm = reactive({
|
||
site_id: 0,
|
||
site_name: '',
|
||
settlement_hold_days: defaultSettlementHoldDays.value,
|
||
})
|
||
|
||
const receiveRatioDialogVisible = ref(false)
|
||
const receiveRatioSubmitting = ref(false)
|
||
const receiveRatioForm = reactive({
|
||
site_id: 0,
|
||
site_name: '',
|
||
receive_cost_delta: 0,
|
||
})
|
||
|
||
const smsDialogVisible = ref(false)
|
||
const smsLoading = ref(false)
|
||
const smsSubmitting = ref(false)
|
||
const smsTesting = ref(false)
|
||
const smsTestCooldown = ref(0)
|
||
let smsCooldownTimer: ReturnType<typeof setInterval> | null = null
|
||
|
||
function startSmsCooldown(seconds = 60) {
|
||
smsTestCooldown.value = seconds
|
||
if (smsCooldownTimer) clearInterval(smsCooldownTimer)
|
||
smsCooldownTimer = setInterval(() => {
|
||
if (smsTestCooldown.value > 0) {
|
||
smsTestCooldown.value--
|
||
} else if (smsCooldownTimer) {
|
||
clearInterval(smsCooldownTimer)
|
||
smsCooldownTimer = null
|
||
}
|
||
}, 1000)
|
||
}
|
||
|
||
onUnmounted(() => {
|
||
if (smsCooldownTimer) clearInterval(smsCooldownTimer)
|
||
})
|
||
|
||
const smsSecretInput = ref('')
|
||
const smsTestPhone = ref('')
|
||
const smsForm = reactive({
|
||
site_id: 0,
|
||
site_name: '',
|
||
enabled: false,
|
||
provider: 'aliyun',
|
||
sign_name: '',
|
||
login_template_code: '',
|
||
access_key_id: '',
|
||
masked_secret: '',
|
||
has_secret: false,
|
||
endpoint: '',
|
||
business_templates: {} as Record<string, string>,
|
||
})
|
||
|
||
function handleOpenSettlement(row: DistributionSite) {
|
||
settlementForm.site_id = row.id
|
||
settlementForm.site_name = row.site_name
|
||
settlementForm.settlement_hold_days = row.settlement_hold_days ?? defaultSettlementHoldDays.value
|
||
settlementDialogVisible.value = true
|
||
}
|
||
|
||
async function handleSettlementSubmit() {
|
||
settlementSubmitting.value = true
|
||
try {
|
||
await updateDistributionSite(settlementForm.site_id, {
|
||
settlement_hold_days: Number(settlementForm.settlement_hold_days),
|
||
})
|
||
ElMessage.success(`【${settlementForm.site_name}】结算冷却期已更新`)
|
||
settlementDialogVisible.value = false
|
||
await loadSites()
|
||
} catch (err) {
|
||
ElMessage.error(readError(err, '更新结算冷却期失败'))
|
||
} finally {
|
||
settlementSubmitting.value = false
|
||
}
|
||
}
|
||
|
||
function handleOpenReceiveRatio(row: DistributionSite) {
|
||
receiveRatioForm.site_id = row.id
|
||
receiveRatioForm.site_name = row.site_name
|
||
receiveRatioForm.receive_cost_delta = Number(row.receive_cost_delta || 0)
|
||
receiveRatioDialogVisible.value = true
|
||
}
|
||
|
||
async function handleReceiveRatioSubmit() {
|
||
receiveRatioSubmitting.value = true
|
||
try {
|
||
await updateDistributionSite(receiveRatioForm.site_id, {
|
||
receive_cost_delta: Number(receiveRatioForm.receive_cost_delta),
|
||
})
|
||
ElMessage.success(`【${receiveRatioForm.site_name}】收号压价已更新`)
|
||
receiveRatioDialogVisible.value = false
|
||
await loadSites()
|
||
} catch (err) {
|
||
ElMessage.error(readError(err, '更新收号比例失败'))
|
||
} finally {
|
||
receiveRatioSubmitting.value = false
|
||
}
|
||
}
|
||
|
||
async function handleOpenSMS(row: DistributionSite) {
|
||
smsForm.site_id = row.id
|
||
smsForm.site_name = row.site_name
|
||
smsSecretInput.value = ''
|
||
smsTestPhone.value = ''
|
||
smsDialogVisible.value = true
|
||
smsLoading.value = true
|
||
try {
|
||
const cfg = await fetchDistributionSiteSMSConfig(row.id)
|
||
smsForm.enabled = cfg.enabled
|
||
smsForm.provider = cfg.provider || 'aliyun'
|
||
smsForm.sign_name = cfg.sign_name || ''
|
||
smsForm.login_template_code = cfg.login_template_code || ''
|
||
smsForm.access_key_id = cfg.access_key_id || ''
|
||
smsForm.masked_secret = cfg.masked_secret || ''
|
||
smsForm.has_secret = cfg.has_secret
|
||
smsForm.endpoint = cfg.endpoint || ''
|
||
// 逐节点初始化,未配置的节点留空表示回退主站模板。
|
||
smsForm.business_templates = Object.fromEntries(
|
||
smsNodeMetas.map(meta => [meta.code, cfg.business_templates?.[meta.code] || ''])
|
||
)
|
||
} catch (err) {
|
||
ElMessage.error(readError(err, '读取分站短信配置失败'))
|
||
} finally {
|
||
smsLoading.value = false
|
||
}
|
||
}
|
||
|
||
function trimmedBusinessTemplates() {
|
||
const out: Record<string, string> = {}
|
||
for (const meta of smsNodeMetas) {
|
||
const code = (smsForm.business_templates[meta.code] || '').trim()
|
||
if (code) out[meta.code] = code
|
||
}
|
||
return out
|
||
}
|
||
|
||
async function handleSMSSubmit() {
|
||
if (smsForm.enabled) {
|
||
if (!smsForm.sign_name.trim()) {
|
||
ElMessage.warning('启用独立短信时,短信签名不能为空')
|
||
return
|
||
}
|
||
if (!smsForm.login_template_code.trim()) {
|
||
ElMessage.warning('启用独立短信时,登录验证码模板 Code 不能为空')
|
||
return
|
||
}
|
||
if (!smsForm.access_key_id.trim()) {
|
||
ElMessage.warning('启用独立短信时,AccessKey ID 不能为空')
|
||
return
|
||
}
|
||
if (!smsForm.has_secret && !smsSecretInput.value.trim()) {
|
||
ElMessage.warning('启用独立短信时,AccessKey Secret 不能为空')
|
||
return
|
||
}
|
||
}
|
||
smsSubmitting.value = true
|
||
try {
|
||
const payload: {
|
||
enabled: boolean
|
||
provider: string
|
||
sign_name: string
|
||
login_template_code: string
|
||
access_key_id: string
|
||
access_key_secret?: string
|
||
endpoint?: string
|
||
business_templates: Record<string, string>
|
||
} = {
|
||
enabled: smsForm.enabled,
|
||
provider: smsForm.provider || 'aliyun',
|
||
sign_name: smsForm.sign_name.trim(),
|
||
login_template_code: smsForm.login_template_code.trim(),
|
||
access_key_id: smsForm.access_key_id.trim(),
|
||
endpoint: smsForm.endpoint.trim(),
|
||
business_templates: trimmedBusinessTemplates(),
|
||
}
|
||
if (smsSecretInput.value.trim()) {
|
||
payload.access_key_secret = smsSecretInput.value.trim()
|
||
}
|
||
await updateDistributionSiteSMSConfig(smsForm.site_id, payload)
|
||
ElMessage.success(`【${smsForm.site_name}】短信配置已保存`)
|
||
smsDialogVisible.value = false
|
||
} catch (err) {
|
||
ElMessage.error(readError(err, '保存短信配置失败'))
|
||
} finally {
|
||
smsSubmitting.value = false
|
||
}
|
||
}
|
||
|
||
async function handleSMSTest() {
|
||
const phone = smsTestPhone.value.trim()
|
||
if (!phone) {
|
||
ElMessage.warning('请输入接收测试短信的手机号')
|
||
return
|
||
}
|
||
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
||
ElMessage.warning('手机号格式不正确,请输入有效的11位手机号码')
|
||
return
|
||
}
|
||
if (!smsForm.access_key_id.trim()) {
|
||
ElMessage.warning('请先填写 AccessKey ID')
|
||
return
|
||
}
|
||
if (!smsForm.has_secret && !smsSecretInput.value.trim()) {
|
||
ElMessage.warning('请先填写 AccessKey Secret')
|
||
return
|
||
}
|
||
if (!smsForm.sign_name.trim()) {
|
||
ElMessage.warning('请先填写短信签名')
|
||
return
|
||
}
|
||
if (!smsForm.login_template_code.trim()) {
|
||
ElMessage.warning('请先填写验证码模板 Code')
|
||
return
|
||
}
|
||
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`将向手机号 ${phone} 发送一条测试验证码,并消耗该站点的阿里云短信额度,是否继续?`,
|
||
'确认发送测试短信',
|
||
{
|
||
confirmButtonText: '确认发送',
|
||
cancelButtonText: '取消',
|
||
type: 'info',
|
||
}
|
||
)
|
||
} catch {
|
||
return
|
||
}
|
||
|
||
smsTesting.value = true
|
||
try {
|
||
const payload = {
|
||
phone,
|
||
enabled: true,
|
||
provider: smsForm.provider || 'aliyun',
|
||
sign_name: smsForm.sign_name.trim(),
|
||
login_template_code: smsForm.login_template_code.trim(),
|
||
access_key_id: smsForm.access_key_id.trim(),
|
||
endpoint: smsForm.endpoint.trim() || undefined,
|
||
access_key_secret: smsSecretInput.value.trim() || undefined,
|
||
}
|
||
await testDistributionSiteSMSConfig(smsForm.site_id, payload)
|
||
ElMessage.success('测试短信发送成功,请查收手机短信')
|
||
startSmsCooldown(60)
|
||
} catch (err) {
|
||
ElMessage.error(readError(err, '测试短信发送失败'))
|
||
} finally {
|
||
smsTesting.value = false
|
||
}
|
||
}
|
||
|
||
const onboardForm = reactive({
|
||
site_name: '',
|
||
site_code: '',
|
||
domain: '',
|
||
admin_username: '',
|
||
admin_password: '',
|
||
admin_nickname: '',
|
||
member_type: 'operator',
|
||
settlement_hold_days: defaultSettlementHoldDays.value,
|
||
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.settlement_hold_days = defaultSettlementHoldDays.value
|
||
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,
|
||
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,
|
||
}
|
||
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后台登录地址:${distributionAdminLoginUrl()}\n管理员账号:${onboardResult.value.member.username}`
|
||
navigator.clipboard?.writeText(text)
|
||
ElMessage.success('已复制分销商开通凭证到剪贴板')
|
||
}
|
||
|
||
function openDistributionAdmin() {
|
||
window.open(distributionAdminBaseUrl(), '_blank')
|
||
}
|
||
|
||
const syncingSiteId = ref<number | null>(null)
|
||
|
||
async function handleSyncAccounts(row: DistributionSite) {
|
||
syncingSiteId.value = row.id
|
||
try {
|
||
const res = await syncDistributionSiteAccounts(row.id)
|
||
const reenabled = res.reenabled_count ? `,恢复展示 ${res.reenabled_count} 个` : ''
|
||
ElMessage.success(
|
||
`同步成功!为【${row.site_name}】新增货架商品 ${res.synced_count} 个,已存在 ${res.skipped_count} 个${reenabled}(本次候选 ${res.total_active} 个)`
|
||
)
|
||
} catch (err) {
|
||
ElMessage.error(readError(err, '同步商品失败'))
|
||
} finally {
|
||
syncingSiteId.value = null
|
||
}
|
||
}
|
||
|
||
// 站点编辑状态
|
||
const editDialogVisible = ref(false)
|
||
const editSubmitting = ref(false)
|
||
const editFormRef = ref<FormInstance>()
|
||
const editForm = reactive({
|
||
id: 0,
|
||
site_code: '',
|
||
site_name: '',
|
||
domain: '',
|
||
status: 'active',
|
||
display_account_enabled: true,
|
||
settlement_hold_days: defaultSettlementHoldDays.value,
|
||
receive_cost_delta: 0,
|
||
tax_enabled: false,
|
||
tax_rate_bps: 100,
|
||
admin_username: '',
|
||
})
|
||
|
||
const editRules: FormRules = {
|
||
site_name: [{ required: true, message: '请输入分销站点名称', trigger: 'blur' }],
|
||
domain: [{ required: true, message: '请输入分销专属域名', trigger: 'blur' }],
|
||
}
|
||
|
||
function handleOpenEdit(row: DistributionSite) {
|
||
editForm.id = row.id
|
||
editForm.site_code = row.site_code
|
||
editForm.site_name = row.site_name
|
||
editForm.domain = row.domain
|
||
editForm.status = row.status || 'active'
|
||
editForm.display_account_enabled = !!row.display_account_enabled
|
||
editForm.settlement_hold_days = row.settlement_hold_days ?? defaultSettlementHoldDays.value
|
||
editForm.receive_cost_delta = Number(row.receive_cost_delta || 0)
|
||
editForm.tax_enabled = !!row.tax_enabled
|
||
editForm.tax_rate_bps = row.tax_rate_bps ?? 100
|
||
editForm.admin_username = row.admin_username || ''
|
||
editDialogVisible.value = true
|
||
}
|
||
|
||
async function handleEditSubmit() {
|
||
if (!editFormRef.value) return
|
||
await editFormRef.value.validate(async valid => {
|
||
if (!valid) return
|
||
editSubmitting.value = true
|
||
try {
|
||
await updateDistributionSite(editForm.id, {
|
||
site_name: editForm.site_name.trim(),
|
||
domain: editForm.domain.trim(),
|
||
status: editForm.status,
|
||
display_account_enabled: editForm.display_account_enabled,
|
||
settlement_hold_days: Number(editForm.settlement_hold_days),
|
||
receive_cost_delta: Number(editForm.receive_cost_delta),
|
||
tax_enabled: editForm.tax_enabled,
|
||
tax_rate_bps: editForm.tax_enabled ? Number(editForm.tax_rate_bps) : 0,
|
||
})
|
||
ElMessage.success(`【${editForm.site_name}】分销站点信息已更新`)
|
||
editDialogVisible.value = false
|
||
await loadSites()
|
||
} catch (err) {
|
||
ElMessage.error(readError(err, '更新分销站点信息失败'))
|
||
} finally {
|
||
editSubmitting.value = false
|
||
}
|
||
})
|
||
}
|
||
|
||
// 快速启停状态
|
||
async function handleToggleStatus(row: DistributionSite) {
|
||
const isCurrentlyActive = row.status === 'active'
|
||
const newStatus = isCurrentlyActive ? 'disabled' : 'active'
|
||
const actionText = isCurrentlyActive ? '停用' : '启用'
|
||
const confirmMsg = isCurrentlyActive
|
||
? `确认停用分销站点【${row.site_name}】?停用后该站点前台将暂停服务,无法进行租号与收号。`
|
||
: `确认重新启用分销站点【${row.site_name}】?启用后该站点将恢复正常对外运营。`
|
||
|
||
try {
|
||
await ElMessageBox.confirm(confirmMsg, `确认${actionText}分销站点`, {
|
||
confirmButtonText: `确认${actionText}`,
|
||
cancelButtonText: '取消',
|
||
type: isCurrentlyActive ? 'warning' : 'info',
|
||
})
|
||
} catch {
|
||
return
|
||
}
|
||
|
||
try {
|
||
await updateDistributionSite(row.id, { status: newStatus })
|
||
ElMessage.success(`分销站点【${row.site_name}】已${actionText}`)
|
||
await loadSites()
|
||
} catch (err) {
|
||
ElMessage.error(readError(err, `${actionText}分销站点失败`))
|
||
}
|
||
}
|
||
|
||
// 重置分销商密码状态
|
||
const resetPwdDialogVisible = ref(false)
|
||
const resetPwdSubmitting = ref(false)
|
||
const resetPwdForm = reactive({
|
||
site_id: 0,
|
||
site_name: '',
|
||
admin_username: '',
|
||
new_password: '',
|
||
})
|
||
|
||
function handleOpenResetPassword(row: DistributionSite) {
|
||
resetPwdForm.site_id = row.id
|
||
resetPwdForm.site_name = row.site_name
|
||
resetPwdForm.admin_username = row.admin_username || '专属管理员'
|
||
resetPwdForm.new_password = ''
|
||
resetPwdDialogVisible.value = true
|
||
}
|
||
|
||
async function handleResetPasswordSubmit() {
|
||
if (!resetPwdForm.new_password || resetPwdForm.new_password.length < 6) {
|
||
ElMessage.warning('新密码长度不能少于6位')
|
||
return
|
||
}
|
||
resetPwdSubmitting.value = true
|
||
try {
|
||
await resetDistributionSiteAdminPassword(resetPwdForm.site_id, resetPwdForm.new_password)
|
||
ElMessage.success(`【${resetPwdForm.site_name}】管理员密码重置成功!`)
|
||
resetPwdDialogVisible.value = false
|
||
} catch (err) {
|
||
ElMessage.error(readError(err, '重置分销商密码失败'))
|
||
} finally {
|
||
resetPwdSubmitting.value = false
|
||
}
|
||
}
|
||
|
||
// 删除分销站点
|
||
async function handleDeleteSite(row: DistributionSite) {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
`确定要删除分销站点【${row.site_name}】(${row.site_code}) 吗?\n此操作将永久清理该分销站及其规则、上架关系与专属管理员账号。\n若该分销站点已产生业务订单或分账流水,系统将予以拦截保护。`,
|
||
'警告:确认删除分销站点',
|
||
{
|
||
confirmButtonText: '确认删除',
|
||
cancelButtonText: '取消',
|
||
type: 'error',
|
||
confirmButtonClass: 'el-button--danger',
|
||
}
|
||
)
|
||
} catch {
|
||
return
|
||
}
|
||
|
||
try {
|
||
await deleteDistributionSite(row.id)
|
||
ElMessage.success(`分销站点【${row.site_name}】已成功删除`)
|
||
await loadSites()
|
||
} catch (err) {
|
||
ElMessage.error(readError(err, '删除分销站点失败'))
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
loadSites()
|
||
fetchDistributionSiteDefaults()
|
||
.then(res => {
|
||
const days = res?.default_settlement_hold_days
|
||
if (typeof days === 'number' && days >= 0 && days <= 365) {
|
||
defaultSettlementHoldDays.value = days
|
||
}
|
||
})
|
||
.catch(() => {
|
||
// 拉取失败时保持内置默认值 7,不弹错误提示
|
||
})
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="distribution-sites-view">
|
||
<div class="page-header">
|
||
<div>
|
||
<h2 class="page-title">分销站点管理</h2>
|
||
<p class="page-subtitle">
|
||
开通分销站、配置结算冷却期与收号比例上限。发布群进群码在「分站二维码池」按站点维护。利润按
|
||
35% 收号 / 40% 卖出 / 25% 服务费切分。
|
||
</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">
|
||
<!-- 搜索筛选栏 -->
|
||
<div class="filter-bar">
|
||
<el-input
|
||
v-model="searchQuery"
|
||
placeholder="搜索站点名称 / 标识 / 域名 / 管理员"
|
||
clearable
|
||
:prefix-icon="Search"
|
||
style="width: 280px"
|
||
/>
|
||
<el-select v-model="statusFilter" placeholder="运行状态" clearable style="width: 130px">
|
||
<el-option label="全部状态" value="" />
|
||
<el-option label="正常运营" value="active" />
|
||
<el-option label="已停用" value="disabled" />
|
||
</el-select>
|
||
<el-select
|
||
v-model="displayAccountFilter"
|
||
placeholder="展示主站账号"
|
||
clearable
|
||
style="width: 140px"
|
||
>
|
||
<el-option label="全部" value="" />
|
||
<el-option label="已开启" value="true" />
|
||
<el-option label="已关闭" value="false" />
|
||
</el-select>
|
||
<el-button @click="handleResetFilter">重置筛选</el-button>
|
||
<el-button :icon="Refresh" @click="loadSites">刷新</el-button>
|
||
<div class="filter-stats">
|
||
共 <strong>{{ filteredSites.length }}</strong> 个分销站点
|
||
</div>
|
||
</div>
|
||
|
||
<el-table v-loading="loading" :data="filteredSites" 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="190">
|
||
<template #default="{ row }">
|
||
<div class="domain-cell">
|
||
<span class="domain-text">{{ row.domain }}</span>
|
||
<el-button
|
||
:icon="DocumentCopy"
|
||
link
|
||
size="small"
|
||
title="复制域名"
|
||
@click="copyDomain(row.domain)"
|
||
/>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="分销管理员" min-width="140">
|
||
<template #default="{ row }">
|
||
<div v-if="row.admin_username" class="admin-cell">
|
||
<el-tag size="small" type="info">{{ row.admin_username }}</el-tag>
|
||
<span v-if="row.admin_nickname" class="admin-nickname"
|
||
>({{ row.admin_nickname }})</span
|
||
>
|
||
</div>
|
||
<span v-else class="text-muted">未关联</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="用户体量" width="130">
|
||
<template #default="{ row }">
|
||
<div class="user-stats-cell">
|
||
<div>
|
||
<strong style="color: #67c23a">{{ row.user_count || 0 }}</strong> 注册
|
||
</div>
|
||
<div class="text-muted" style="font-size: 12px">
|
||
{{ row.order_user_count || 0 }} 成交客户
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="运行状态" prop="status" width="110">
|
||
<template #default="{ row }">
|
||
<el-tooltip
|
||
:content="row.status === 'active' ? '点击可停用分销站点' : '点击可启用分销站点'"
|
||
placement="top"
|
||
>
|
||
<el-switch
|
||
:model-value="row.status === 'active'"
|
||
inline-prompt
|
||
active-text="正常"
|
||
inactive-text="停用"
|
||
@click.prevent="handleToggleStatus(row)"
|
||
/>
|
||
</el-tooltip>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="展示主站账号" prop="display_account_enabled" width="120">
|
||
<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="110">
|
||
<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="结算冷却期" width="110">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.settlement_hold_days > 0 ? 'primary' : 'success'" size="small">
|
||
{{ row.settlement_hold_days > 0 ? `${row.settlement_hold_days} 天` : '立即释放' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="收号压价" width="110">
|
||
<template #default="{ row }">
|
||
<el-tag
|
||
:type="Number(row.receive_cost_delta || 0) > 0 ? 'warning' : 'info'"
|
||
size="small"
|
||
>
|
||
{{
|
||
Number(row.receive_cost_delta || 0) > 0
|
||
? `低 ${Number(row.receive_cost_delta).toFixed(1)} 点`
|
||
: '与主站一致'
|
||
}}
|
||
</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="220" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link :icon="Edit" @click="handleOpenEdit(row)">
|
||
编辑
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
link
|
||
:icon="Refresh"
|
||
:loading="syncingSiteId === row.id"
|
||
@click="handleSyncAccounts(row)"
|
||
>
|
||
同步商品
|
||
</el-button>
|
||
<el-dropdown trigger="click">
|
||
<el-button type="primary" link>
|
||
更多<el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
||
</el-button>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item :icon="Clock" @click="handleOpenSettlement(row)">
|
||
结算设置
|
||
</el-dropdown-item>
|
||
<el-dropdown-item :icon="Coin" @click="handleOpenReceiveRatio(row)">
|
||
收号比例
|
||
</el-dropdown-item>
|
||
<el-dropdown-item :icon="Message" @click="handleOpenSMS(row)">
|
||
短信配置
|
||
</el-dropdown-item>
|
||
<el-dropdown-item :icon="Key" @click="handleOpenResetPassword(row)">
|
||
重置管理员密码
|
||
</el-dropdown-item>
|
||
<el-dropdown-item
|
||
divided
|
||
:icon="Delete"
|
||
style="color: #f56c6c"
|
||
@click="handleDeleteSite(row)"
|
||
>
|
||
删除站点
|
||
</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</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-alert
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
title="开通后四种分成模式均按默认生效:收号 35% / 卖出 40% / 服务费 25%(本站自卖为 75% + 25%)。之后可在「分销规则」页单独调整。"
|
||
style="margin-bottom: 12px"
|
||
/>
|
||
<el-row :gutter="16">
|
||
<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-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>{{ distributionAdminLoginUrl() }}</code>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="初始分成规则">
|
||
四种模式均已按默认生效:收号 35% / 卖出 40% / 服务费 25%(本站自卖 75% +
|
||
25%),可到规则页调整。
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="结算冷却期">
|
||
{{ onboardResult.site.settlement_hold_days }} 天
|
||
</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>
|
||
|
||
<!-- 结算冷却期设置对话框 -->
|
||
<el-dialog
|
||
v-model="settlementDialogVisible"
|
||
title="结算冷却期设置"
|
||
width="480px"
|
||
destroy-on-close
|
||
>
|
||
<el-alert
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
title="订单完成后进入冷却期,期满自动释放到商户可提现余额;0 天表示完成即释放。修改后对尚未释放的已完成订单立即生效。"
|
||
style="margin-bottom: 16px"
|
||
/>
|
||
<el-form label-width="110px">
|
||
<el-form-item label="分销站点">
|
||
<el-input :model-value="settlementForm.site_name" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="冷却天数">
|
||
<el-input-number
|
||
v-model="settlementForm.settlement_hold_days"
|
||
:min="0"
|
||
:max="365"
|
||
:step="1"
|
||
/>
|
||
<span class="settlement-hint">天(0-365)</span>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="settlementDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="settlementSubmitting" @click="handleSettlementSubmit">
|
||
保存设置
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 分站收号压价(收号价始终不高于主站参考价) -->
|
||
<el-dialog
|
||
v-model="receiveRatioDialogVisible"
|
||
title="分站收号压价"
|
||
width="520px"
|
||
destroy-on-close
|
||
>
|
||
<el-alert
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
title="分站收号价比主站更低:压价点数越大,号主在分站拿到的收号价越低。买家售价保持与主站一致,压价差进入三方分成(35% 收号 / 40% 卖出 / 25% 服务费)。0 表示与主站同价收号。"
|
||
style="margin-bottom: 16px"
|
||
/>
|
||
<el-form label-width="110px">
|
||
<el-form-item label="分销站点">
|
||
<el-input :model-value="receiveRatioForm.site_name" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="收号价比主站低">
|
||
<el-input-number
|
||
v-model="receiveRatioForm.receive_cost_delta"
|
||
:min="0"
|
||
:max="20"
|
||
:step="0.1"
|
||
:precision="1"
|
||
/>
|
||
<span class="settlement-hint">点(0-20)</span>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="receiveRatioDialogVisible = false">取消</el-button>
|
||
<el-button
|
||
type="primary"
|
||
:loading="receiveRatioSubmitting"
|
||
@click="handleReceiveRatioSubmit"
|
||
>
|
||
保存设置
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 短信配置对话框 -->
|
||
<el-dialog
|
||
v-model="smsDialogVisible"
|
||
title="分销站点独立短信服务配置"
|
||
width="620px"
|
||
destroy-on-close
|
||
>
|
||
<div v-loading="smsLoading">
|
||
<el-alert
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
title="启用后,该分站用户的登录注册验证码将使用独立的签名与模板,并直接计入该分站的阿里云短信额度;未启用时自动回退使用主站默认短信通道。"
|
||
style="margin-bottom: 16px"
|
||
/>
|
||
<el-form label-width="130px">
|
||
<el-form-item label="分销站点">
|
||
<el-input :model-value="smsForm.site_name" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="启用独立短信">
|
||
<el-switch v-model="smsForm.enabled" />
|
||
<span style="margin-left: 12px; font-size: 13px; color: #6b7280">
|
||
{{ smsForm.enabled ? '已启用(优先使用分站凭据)' : '未启用(回退使用主站额度)' }}
|
||
</span>
|
||
</el-form-item>
|
||
<el-form-item label="短信服务商">
|
||
<el-select v-model="smsForm.provider" style="width: 100%">
|
||
<el-option label="阿里云短信服务 (Aliyun Dysms)" value="aliyun" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="短信签名" :required="smsForm.enabled">
|
||
<el-input v-model="smsForm.sign_name" placeholder="例如:XX租号" />
|
||
</el-form-item>
|
||
<el-form-item label="验证码模板 Code" :required="smsForm.enabled">
|
||
<el-input v-model="smsForm.login_template_code" placeholder="例如:SMS_123456789" />
|
||
</el-form-item>
|
||
<el-form-item label="AccessKey ID" :required="smsForm.enabled">
|
||
<el-input v-model="smsForm.access_key_id" placeholder="例如:LTAI5t..." />
|
||
</el-form-item>
|
||
<el-form-item label="AccessKey Secret" :required="smsForm.enabled && !smsForm.has_secret">
|
||
<el-input
|
||
v-model="smsSecretInput"
|
||
type="password"
|
||
show-password
|
||
:placeholder="
|
||
smsForm.has_secret
|
||
? `已设置密钥 (${smsForm.masked_secret}),留空保持不变`
|
||
: '请输入 AccessKey Secret'
|
||
"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="API Endpoint">
|
||
<el-input v-model="smsForm.endpoint" placeholder="默认 dysmsapi.aliyuncs.com" />
|
||
</el-form-item>
|
||
<el-divider content-position="left">节点短信模板(可选)</el-divider>
|
||
<el-form-item v-for="meta in smsNodeMetas" :key="meta.code" :label="meta.name">
|
||
<el-input
|
||
v-model="smsForm.business_templates[meta.code]"
|
||
:placeholder="`留空则用主站模板,例如:${meta.templateCode}`"
|
||
/>
|
||
<div class="sms-node-hint">{{ meta.description }}</div>
|
||
</el-form-item>
|
||
<el-form-item label="连通性测试">
|
||
<div style="display: flex; gap: 8px; width: 100%">
|
||
<el-input v-model="smsTestPhone" placeholder="输入接收测试短信的手机号" />
|
||
<el-button
|
||
:loading="smsTesting"
|
||
:disabled="smsTestCooldown > 0"
|
||
@click="handleSMSTest"
|
||
>
|
||
{{ smsTestCooldown > 0 ? `重新发送(${smsTestCooldown}s)` : '发送测试' }}
|
||
</el-button>
|
||
</div>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="smsDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="smsSubmitting" @click="handleSMSSubmit">
|
||
保存设置
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 编辑分销站点对话框 -->
|
||
<el-dialog v-model="editDialogVisible" title="编辑分销站点" width="620px" destroy-on-close>
|
||
<el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="130px">
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="站点标识">
|
||
<el-tag size="default" type="info">{{ editForm.site_code }}</el-tag>
|
||
<span style="margin-left: 8px; font-size: 12px; color: #9ca3af">不可更改</span>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="分销管理员">
|
||
<span v-if="editForm.admin_username" style="font-weight: 500">
|
||
{{ editForm.admin_username }}
|
||
</span>
|
||
<span v-else style="color: #9ca3af">未关联</span>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-form-item label="站点名称" prop="site_name">
|
||
<el-input v-model="editForm.site_name" placeholder="请输入分销站点名称" />
|
||
</el-form-item>
|
||
|
||
<el-form-item label="分销专属域名" prop="domain">
|
||
<el-input v-model="editForm.domain" placeholder="请输入绑定的独立域名" />
|
||
</el-form-item>
|
||
|
||
<el-form-item label="运行状态" prop="status">
|
||
<el-radio-group v-model="editForm.status">
|
||
<el-radio value="active">正常运营</el-radio>
|
||
<el-radio value="disabled">暂停服务 (已停用)</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="展示主站账号">
|
||
<el-switch v-model="editForm.display_account_enabled" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="是否征税">
|
||
<el-switch v-model="editForm.tax_enabled" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-row :gutter="16" v-if="editForm.tax_enabled">
|
||
<el-col :span="24">
|
||
<el-form-item label="综合税率 (%)">
|
||
<el-input-number
|
||
:model-value="editForm.tax_rate_bps / 100"
|
||
:min="0"
|
||
:max="50"
|
||
:step="0.1"
|
||
:precision="2"
|
||
@update:model-value="
|
||
(v: number | undefined) => (editForm.tax_rate_bps = Math.round((v || 0) * 100))
|
||
"
|
||
/>
|
||
<span class="settlement-hint">%(基点折算)</span>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="结算冷却期 (天)">
|
||
<el-input-number
|
||
v-model="editForm.settlement_hold_days"
|
||
:min="0"
|
||
:max="365"
|
||
:step="1"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="收号压价 (点)">
|
||
<el-input-number
|
||
v-model="editForm.receive_cost_delta"
|
||
:min="0"
|
||
:max="20"
|
||
:step="0.1"
|
||
:precision="1"
|
||
style="width: 100%"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="editDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="editSubmitting" @click="handleEditSubmit">
|
||
保存修改
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 重置分销商管理员密码对话框 -->
|
||
<el-dialog
|
||
v-model="resetPwdDialogVisible"
|
||
title="重置分销商管理员密码"
|
||
width="460px"
|
||
destroy-on-close
|
||
>
|
||
<el-alert
|
||
type="warning"
|
||
:closable="false"
|
||
show-icon
|
||
title="重置后旧密码将立即失效,分销商管理员需使用新密码重新登录独立分销后台。"
|
||
style="margin-bottom: 16px"
|
||
/>
|
||
<el-form label-width="110px">
|
||
<el-form-item label="分销站点">
|
||
<el-input :model-value="resetPwdForm.site_name" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="管理员账号">
|
||
<el-input :model-value="resetPwdForm.admin_username" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="新登录密码" required>
|
||
<el-input
|
||
v-model="resetPwdForm.new_password"
|
||
type="password"
|
||
show-password
|
||
placeholder="请输入至少6位新登录密码"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="resetPwdDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="resetPwdSubmitting" @click="handleResetPasswordSubmit">
|
||
确认重置密码
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<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;
|
||
}
|
||
|
||
.filter-bar {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.filter-stats {
|
||
margin-left: auto;
|
||
font-size: 13px;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.filter-stats strong {
|
||
color: #1f2937;
|
||
}
|
||
|
||
.domain-cell {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.domain-text {
|
||
font-family: monospace;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.admin-cell {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.admin-nickname {
|
||
font-size: 12px;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.text-muted {
|
||
color: #9ca3af;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.settlement-hint {
|
||
margin-left: 8px;
|
||
color: #6b7280;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.sms-node-hint {
|
||
width: 100%;
|
||
margin-top: 4px;
|
||
color: #9ca3af;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
}
|
||
</style>
|