Files
one-pipe-system/src/views/package-management/series-grants/detail.vue
sexygoat b510b4539f
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 11m14s
修复bug
2026-04-08 10:19:25 +08:00

667 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>