667 lines
21 KiB
Vue
667 lines
21 KiB
Vue
<template>
|
||
<div class="series-grants-detail">
|
||
<ElCard shadow="never">
|
||
<!-- 页面头部 -->
|
||
<div class="detail-header">
|
||
<ElButton @click="handleBack">
|
||
<template #icon>
|
||
<ElIcon><ArrowLeft /></ElIcon>
|
||
</template>
|
||
返回
|
||
</ElButton>
|
||
<h2 class="detail-title">代理系列授权详情</h2>
|
||
</div>
|
||
|
||
<!-- 详情内容 -->
|
||
<div v-if="detailData" class="detail-content">
|
||
<!-- 基本信息 -->
|
||
<div class="info-section">
|
||
<div class="section-title">基本信息</div>
|
||
<ElDescriptions :column="2" border>
|
||
<ElDescriptionsItem label="ID">{{ detailData.id }}</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="系列编码">{{
|
||
detailData.series_code || '-'
|
||
}}</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="系列名称">{{ detailData.series_name }}</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="店铺名称">{{ detailData.shop_name }}</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="分配者店铺">
|
||
<ElTag v-if="detailData.allocator_shop_id === 0" type="primary">
|
||
{{ detailData.allocator_shop_name || '平台' }}
|
||
</ElTag>
|
||
<span v-else>{{ detailData.allocator_shop_name || '-' }}</span>
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="状态">
|
||
<ElTag :type="detailData.status === 1 ? 'success' : 'danger'">
|
||
{{ detailData.status === 1 ? '启用' : '禁用' }}
|
||
</ElTag>
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="创建时间">{{
|
||
formatDateTime(detailData.created_at)
|
||
}}</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="更新时间">{{
|
||
formatDateTime(detailData.updated_at)
|
||
}}</ElDescriptionsItem>
|
||
</ElDescriptions>
|
||
</div>
|
||
|
||
<!-- 佣金配置 -->
|
||
<div class="info-section">
|
||
<div class="section-title">佣金配置</div>
|
||
<ElDescriptions :column="2" border label-width="120">
|
||
<ElDescriptionsItem label="佣金类型">
|
||
<ElTag :type="detailData.commission_type === 'fixed' ? 'success' : 'warning'">
|
||
{{ detailData.commission_type === 'fixed' ? '固定佣金' : '梯度佣金' }}
|
||
</ElTag>
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="固定佣金金额" v-if="detailData.commission_type === 'fixed'">
|
||
<span v-if="detailData.one_time_commission_amount" class="amount-value">
|
||
¥{{ (detailData.one_time_commission_amount / 100).toFixed(2) }}
|
||
</span>
|
||
<span v-else>-</span>
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem
|
||
label="梯度配置"
|
||
:span="1"
|
||
v-if="detailData.commission_type === 'tiered'"
|
||
>
|
||
<div
|
||
v-if="detailData.commission_tiers && detailData.commission_tiers.length > 0"
|
||
class="tier-table-wrapper"
|
||
>
|
||
<ElTable :data="tierTableData" border size="small" style="width: 100%">
|
||
<ElTableColumn label="档位" width="80" align="center">
|
||
<template #default="{ $index }">
|
||
<ElTag type="primary" size="small">档位{{ $index + 1 }}</ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="达标阈值" width="120">
|
||
<template #default="{ row }">
|
||
<span class="threshold-value"
|
||
>{{ row.operator }} {{ formatThreshold(row) }}</span
|
||
>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="统计维度" width="110">
|
||
<template #default="{ row }">
|
||
<ElTag size="small" type="info">
|
||
{{ row.dimension === 'sales_count' ? '销量' : '销售额' }}
|
||
</ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="统计范围" width="120">
|
||
<template #default="{ row }">
|
||
<ElTag size="small" type="warning">
|
||
仅自己
|
||
<!--{{-->
|
||
<!-- row.stat_scope === 'self'-->
|
||
<!-- ? '仅自己'-->
|
||
<!-- : row.stat_scope === 'self_and_sub'-->
|
||
<!-- ? '自己+下级'-->
|
||
<!-- : '-'-->
|
||
<!--}}-->
|
||
</ElTag>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="佣金金额" min-width="100">
|
||
<template #default="{ row }">
|
||
<span class="amount-value">¥{{ (row.amount / 100).toFixed(2) }}</span>
|
||
</template>
|
||
</ElTableColumn>
|
||
</ElTable>
|
||
</div>
|
||
<span v-else>-</span>
|
||
</ElDescriptionsItem>
|
||
</ElDescriptions>
|
||
</div>
|
||
|
||
<!-- 强制充值配置 -->
|
||
<div class="info-section">
|
||
<div class="section-title">强制充值配置</div>
|
||
<ElDescriptions :column="2" border>
|
||
<ElDescriptionsItem label="启用状态">
|
||
<ElTag :type="detailData.force_recharge_enabled ? 'warning' : 'info'">
|
||
{{ getEnableStatusText(detailData.force_recharge_enabled || false) }}
|
||
</ElTag>
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="锁定状态">
|
||
<ElTag :type="detailData.force_recharge_locked ? 'danger' : 'success'">
|
||
{{ detailData.force_recharge_locked ? '已锁定(平台控制)' : '未锁定' }}
|
||
</ElTag>
|
||
</ElDescriptionsItem>
|
||
<ElDescriptionsItem label="强充金额" :span="2">
|
||
<span v-if="detailData.force_recharge_amount" class="amount-value">
|
||
¥{{ (detailData.force_recharge_amount / 100).toFixed(2) }}
|
||
</span>
|
||
<span v-else>-</span>
|
||
</ElDescriptionsItem>
|
||
</ElDescriptions>
|
||
</div>
|
||
|
||
<!-- 套餐信息 -->
|
||
<div class="info-section">
|
||
<div class="section-title">
|
||
<span>套餐列表</span>
|
||
<ElButton
|
||
type="primary"
|
||
size="small"
|
||
@click="showAddPackageDialog"
|
||
v-permission="'series_grants:manage_packages'"
|
||
>
|
||
添加授权套餐
|
||
</ElButton>
|
||
</div>
|
||
|
||
<!-- 套餐列表 -->
|
||
<div v-if="detailData.packages && detailData.packages.length > 0" class="package-table">
|
||
<ElTable :data="detailData.packages" border stripe>
|
||
<ElTableColumn prop="package_name" label="套餐名称" />
|
||
<ElTableColumn prop="package_code" label="套餐编码" />
|
||
<ElTableColumn label="成本价">
|
||
<template #default="{ row }">
|
||
<span class="amount-value">¥{{ (row.cost_price / 100).toFixed(2) }}</span>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="上架状态" align="center">
|
||
<template #default="{ row }">
|
||
<ElTag v-if="row.shelf_status === 1" type="success" size="small">上架</ElTag>
|
||
<ElTag v-else-if="row.shelf_status === 2" type="info" size="small">下架</ElTag>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="状态" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<ElTag v-if="row.status === 1" type="success" size="small">启用</ElTag>
|
||
<ElTag v-else-if="row.status === 2" type="danger" size="small">禁用</ElTag>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</ElTableColumn>
|
||
<ElTableColumn label="操作" width="150" align="center" fixed="right">
|
||
<template #default="{ row }">
|
||
<ElButton
|
||
type="primary"
|
||
size="small"
|
||
link
|
||
@click="showEditPackageDialog(row)"
|
||
v-permission="'series_grants:edit_packages'"
|
||
>
|
||
编辑
|
||
</ElButton>
|
||
<ElButton
|
||
type="danger"
|
||
size="small"
|
||
link
|
||
@click="handleDeletePackage(row)"
|
||
v-permission="'series_grants:delete_packages'"
|
||
>
|
||
删除
|
||
</ElButton>
|
||
</template>
|
||
</ElTableColumn>
|
||
</ElTable>
|
||
</div>
|
||
<ElEmpty v-else description="暂无套餐" :image-size="80" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 加载中 -->
|
||
<div v-if="loading" class="loading-container">
|
||
<ElIcon class="is-loading"><Loading /></ElIcon>
|
||
<span>加载中...</span>
|
||
</div>
|
||
|
||
<!-- 添加/编辑套餐对话框 -->
|
||
<ElDialog
|
||
v-model="packageDialogVisible"
|
||
:title="packageDialogType === 'add' ? '添加套餐' : '编辑套餐'"
|
||
width="500px"
|
||
:close-on-click-modal="false"
|
||
@closed="handlePackageDialogClosed"
|
||
>
|
||
<ElForm ref="packageFormRef" :model="packageForm" :rules="packageRules" label-width="100px">
|
||
<!-- 添加模式:选择套餐 -->
|
||
<ElFormItem label="选择套餐" prop="package_id" v-if="packageDialogType === 'add'">
|
||
<ElSelect
|
||
v-model="packageForm.package_id"
|
||
placeholder="请选择套餐"
|
||
style="width: 100%"
|
||
filterable
|
||
remote
|
||
:remote-method="searchPackages"
|
||
:loading="packageLoading"
|
||
clearable
|
||
>
|
||
<template
|
||
v-if="availablePackages.length === 0 && !packageLoading && detailData?.series_id"
|
||
>
|
||
<ElOption disabled value="" label="该系列没有可选套餐" />
|
||
</template>
|
||
<ElOption
|
||
v-for="pkg in availablePackages"
|
||
:key="pkg.id"
|
||
:label="`${pkg.package_name} (${pkg.package_code})`"
|
||
:value="pkg.id"
|
||
:disabled="isPackageAlreadyAdded(pkg.id)"
|
||
/>
|
||
</ElSelect>
|
||
</ElFormItem>
|
||
|
||
<!-- 编辑模式:显示套餐信息 -->
|
||
<ElFormItem label="套餐名称" v-if="packageDialogType === 'edit'">
|
||
<span>{{ packageForm.package_name }}</span>
|
||
</ElFormItem>
|
||
<ElFormItem label="套餐编码" v-if="packageDialogType === 'edit'">
|
||
<span>{{ packageForm.package_code }}</span>
|
||
</ElFormItem>
|
||
|
||
<!-- 成本价 -->
|
||
<ElFormItem label="成本价(元)" prop="cost_price_yuan">
|
||
<ElInputNumber
|
||
v-model="packageForm.cost_price_yuan"
|
||
:min="0"
|
||
:precision="2"
|
||
:step="0.01"
|
||
:controls="false"
|
||
style="width: 100%"
|
||
placeholder="请输入成本价"
|
||
/>
|
||
</ElFormItem>
|
||
</ElForm>
|
||
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="packageDialogVisible = false">取消</ElButton>
|
||
<ElButton type="primary" @click="handleSavePackage" :loading="submitLoading">
|
||
保存
|
||
</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
</ElCard>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, computed } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import {
|
||
ElCard,
|
||
ElButton,
|
||
ElIcon,
|
||
ElMessage,
|
||
ElMessageBox,
|
||
ElDescriptions,
|
||
ElDescriptionsItem,
|
||
ElTag,
|
||
ElTable,
|
||
ElTableColumn,
|
||
ElEmpty,
|
||
ElDialog,
|
||
ElSelect,
|
||
ElOption,
|
||
ElInputNumber,
|
||
ElForm,
|
||
ElFormItem,
|
||
FormInstance,
|
||
FormRules
|
||
} from 'element-plus'
|
||
import { ArrowLeft, Loading, Setting, List, Plus } from '@element-plus/icons-vue'
|
||
import { ShopSeriesGrantService, PackageManageService, PackageSeriesService } from '@/api/modules'
|
||
import type {
|
||
ShopSeriesGrantResponse,
|
||
PackageResponse,
|
||
GrantPackageItem,
|
||
GrantPackageInfo,
|
||
PackageSeriesResponse
|
||
} from '@/types/api'
|
||
import { formatDateTime } from '@/utils/business/format'
|
||
import { getEnableStatusText } from '@/config/constants'
|
||
|
||
defineOptions({ name: 'SeriesGrantsDetail' })
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
|
||
const loading = ref(false)
|
||
const submitLoading = ref(false)
|
||
const packageLoading = ref(false)
|
||
const detailData = ref<ShopSeriesGrantResponse | null>(null)
|
||
const seriesData = ref<PackageSeriesResponse | null>(null)
|
||
const packageDialogVisible = ref(false)
|
||
const packageDialogType = ref<'add' | 'edit'>('add')
|
||
const availablePackages = ref<PackageResponse[]>([])
|
||
const packageFormRef = ref<FormInstance>()
|
||
|
||
// 套餐表单
|
||
const packageForm = ref<{
|
||
package_id?: number
|
||
package_name?: string
|
||
package_code?: string
|
||
cost_price_yuan: number
|
||
}>({
|
||
cost_price_yuan: 0
|
||
})
|
||
|
||
// 表单验证规则
|
||
const packageRules = computed<FormRules>(() => ({
|
||
package_id: [
|
||
{ required: packageDialogType.value === 'add', message: '请选择套餐', trigger: 'change' }
|
||
],
|
||
cost_price_yuan: [
|
||
{ required: true, message: '请输入成本价', trigger: 'blur' },
|
||
{ type: 'number', min: 0, message: '成本价不能小于0', trigger: 'blur' }
|
||
]
|
||
}))
|
||
|
||
// 梯度配置表格数据(合并授权数据和系列数据)
|
||
const tierTableData = computed(() => {
|
||
if (
|
||
!detailData.value?.commission_tiers ||
|
||
!seriesData.value?.one_time_commission_config?.tiers
|
||
) {
|
||
return []
|
||
}
|
||
|
||
const grantTiers = detailData.value.commission_tiers
|
||
const seriesTiers = seriesData.value.one_time_commission_config.tiers
|
||
|
||
return grantTiers.map((grantTier, index) => {
|
||
const seriesTier = seriesTiers[index] || {}
|
||
return {
|
||
operator: grantTier.operator || '>=',
|
||
threshold: grantTier.threshold,
|
||
dimension: seriesTier.dimension || 'sales_count',
|
||
stat_scope: seriesTier.stat_scope,
|
||
amount: grantTier.amount
|
||
}
|
||
})
|
||
})
|
||
|
||
// 格式化达标阈值显示
|
||
const formatThreshold = (row: any) => {
|
||
if (row.dimension === 'sales_amount') {
|
||
// 销售额维度,阈值单位是分,需要转换为元显示
|
||
return `¥${(row.threshold / 100).toFixed(2)}`
|
||
} else {
|
||
// 销量维度,直接显示数字
|
||
return `${row.threshold}件`
|
||
}
|
||
}
|
||
|
||
// 返回上一页
|
||
const handleBack = () => {
|
||
router.back()
|
||
}
|
||
|
||
// 获取详情数据
|
||
const fetchDetail = async () => {
|
||
const id = Number(route.params.id)
|
||
if (!id) {
|
||
ElMessage.error('缺少ID参数')
|
||
return
|
||
}
|
||
|
||
loading.value = true
|
||
try {
|
||
const res = await ShopSeriesGrantService.getShopSeriesGrantDetail(id)
|
||
if (res.code === 0) {
|
||
detailData.value = res.data
|
||
|
||
// 如果是梯度佣金类型,获取系列配置以显示完整的梯度信息
|
||
if (res.data.commission_type === 'tiered' && res.data.series_id) {
|
||
await fetchSeriesData(res.data.series_id)
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
ElMessage.error('获取详情失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 获取系列配置数据
|
||
const fetchSeriesData = async (seriesId: number) => {
|
||
try {
|
||
const res = await PackageSeriesService.getPackageSeriesDetail(seriesId)
|
||
if (res.code === 0) {
|
||
seriesData.value = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取系列配置失败:', error)
|
||
}
|
||
}
|
||
|
||
// 显示添加套餐对话框
|
||
const showAddPackageDialog = () => {
|
||
packageDialogType.value = 'add'
|
||
packageForm.value = {
|
||
package_id: undefined,
|
||
cost_price_yuan: 0
|
||
}
|
||
|
||
// 加载可用套餐
|
||
if (detailData.value?.series_id) {
|
||
loadAvailablePackages()
|
||
}
|
||
|
||
packageDialogVisible.value = true
|
||
}
|
||
|
||
// 显示编辑套餐对话框
|
||
const showEditPackageDialog = (row: GrantPackageInfo) => {
|
||
packageDialogType.value = 'edit'
|
||
packageForm.value = {
|
||
package_id: row.package_id,
|
||
package_name: row.package_name,
|
||
package_code: row.package_code,
|
||
cost_price_yuan: row.cost_price / 100
|
||
}
|
||
|
||
packageDialogVisible.value = true
|
||
}
|
||
|
||
// 加载可用套餐
|
||
const loadAvailablePackages = async (packageName?: string) => {
|
||
if (!detailData.value?.series_id) return
|
||
|
||
packageLoading.value = true
|
||
try {
|
||
const params: any = {
|
||
page: 1,
|
||
page_size: 50,
|
||
series_id: detailData.value.series_id
|
||
}
|
||
|
||
if (packageName) {
|
||
params.package_name = packageName
|
||
}
|
||
|
||
const res = await PackageManageService.getPackages(params)
|
||
if (res.code === 0) {
|
||
availablePackages.value = res.data.items
|
||
}
|
||
} catch (error) {
|
||
console.error('加载套餐选项失败:', error)
|
||
} finally {
|
||
packageLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 搜索套餐
|
||
const searchPackages = (query: string) => {
|
||
if (query) {
|
||
loadAvailablePackages(query)
|
||
} else {
|
||
loadAvailablePackages()
|
||
}
|
||
}
|
||
|
||
// 检查套餐是否已添加
|
||
const isPackageAlreadyAdded = (packageId: number) => {
|
||
return detailData.value?.packages?.some((p) => p.package_id === packageId) || false
|
||
}
|
||
|
||
// 保存套餐
|
||
const handleSavePackage = async () => {
|
||
if (!packageFormRef.value || !detailData.value) return
|
||
|
||
const grantId = detailData.value.id // 保存到常量中避免类型检查问题
|
||
await packageFormRef.value.validate(async (valid) => {
|
||
if (!valid) return
|
||
|
||
submitLoading.value = true
|
||
try {
|
||
const packages: GrantPackageItem[] = []
|
||
|
||
if (packageDialogType.value === 'add') {
|
||
// 添加模式:添加新套餐
|
||
packages.push({
|
||
package_id: packageForm.value.package_id,
|
||
cost_price: Math.round(packageForm.value.cost_price_yuan * 100)
|
||
})
|
||
} else {
|
||
// 编辑模式:更新套餐成本价
|
||
packages.push({
|
||
package_id: packageForm.value.package_id,
|
||
cost_price: Math.round(packageForm.value.cost_price_yuan * 100)
|
||
})
|
||
}
|
||
|
||
await ShopSeriesGrantService.manageGrantPackages(grantId, { packages })
|
||
ElMessage.success(packageDialogType.value === 'add' ? '添加成功' : '更新成功')
|
||
packageDialogVisible.value = false
|
||
|
||
// 刷新详情数据
|
||
await fetchDetail()
|
||
} catch (error) {
|
||
console.error(error)
|
||
ElMessage.error(packageDialogType.value === 'add' ? '添加失败' : '更新失败')
|
||
} finally {
|
||
submitLoading.value = false
|
||
}
|
||
})
|
||
}
|
||
|
||
// 删除套餐
|
||
const handleDeletePackage = (row: GrantPackageInfo) => {
|
||
ElMessageBox.confirm(`确定删除套餐 ${row.package_name} 的授权吗?`, '删除确认', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
})
|
||
.then(async () => {
|
||
if (!detailData.value) return
|
||
|
||
submitLoading.value = true
|
||
try {
|
||
const packages: GrantPackageItem[] = [
|
||
{
|
||
package_id: row.package_id,
|
||
remove: true
|
||
}
|
||
]
|
||
|
||
await ShopSeriesGrantService.manageGrantPackages(detailData.value.id, { packages })
|
||
ElMessage.success('删除成功')
|
||
|
||
// 刷新详情数据
|
||
await fetchDetail()
|
||
} catch (error) {
|
||
console.error(error)
|
||
ElMessage.error('删除失败')
|
||
} finally {
|
||
submitLoading.value = false
|
||
}
|
||
})
|
||
.catch(() => {
|
||
// 用户取消
|
||
})
|
||
}
|
||
|
||
// 关闭对话框
|
||
const handlePackageDialogClosed = () => {
|
||
packageFormRef.value?.resetFields()
|
||
packageForm.value = {
|
||
cost_price_yuan: 0
|
||
}
|
||
availablePackages.value = []
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchDetail()
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.series-grants-detail {
|
||
padding: 20px;
|
||
}
|
||
|
||
.detail-header {
|
||
display: flex;
|
||
gap: 16px;
|
||
align-items: center;
|
||
padding-bottom: 16px;
|
||
margin-bottom: 20px;
|
||
border-bottom: 1px solid var(--el-border-color);
|
||
|
||
.detail-title {
|
||
margin: 0;
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
}
|
||
|
||
.detail-content {
|
||
.info-section {
|
||
margin-bottom: 24px;
|
||
|
||
.section-title {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding-bottom: 8px;
|
||
margin-bottom: 12px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
.package-table {
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.tier-table-wrapper {
|
||
.threshold-value {
|
||
font-weight: 500;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.loading-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 60px 20px;
|
||
color: var(--el-text-color-secondary);
|
||
|
||
.el-icon {
|
||
font-size: 32px;
|
||
}
|
||
}
|
||
|
||
.amount-value {
|
||
font-weight: 600;
|
||
color: var(--el-color-warning);
|
||
}
|
||
|
||
.dialog-footer {
|
||
text-align: right;
|
||
}
|
||
</style>
|