修改bug
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m54s

This commit is contained in:
sexygoat
2026-04-09 13:48:11 +08:00
parent cc9d2a85a2
commit cb3ba06106
26 changed files with 987 additions and 1984 deletions

View File

@@ -690,15 +690,17 @@
label: '虚流量比例',
width: 120,
formatter: (row: PackageResponse) => {
// 如果启用虚流量且流量大于0计算比
// 如果启用虚流量且流量大于0计算虚量百分
const virtualData = row.virtual_data_mb ?? 0
const realData = row.real_data_mb ?? 0
if (row.enable_virtual_data && virtualData > 0) {
const ratio = realData / virtualData
return ratio.toFixed(2)
if (row.enable_virtual_data && realData > 0 && virtualData > 0) {
// 虚量百分比 = (1 - 虚流量/真流量) * 100%
// 例如真流量100G虚流量70G则虚量百分比 = (1 - 70/100) * 100% = 30%
const ratio = (1 - virtualData / realData) * 100
return `${ratio.toFixed(2)}%`
}
// 否则返回 1.0
return '1.00'
// 否则返回 0%
return '0%'
}
},
{

View File

@@ -174,13 +174,18 @@
<ElFormItem label="成本价(元)" prop="cost_price_yuan">
<ElInputNumber
v-model="packageForm.cost_price_yuan"
:min="0"
:min="packageForm.original_cost_price || 0"
:max="packageForm.original_cost_price ? packageForm.original_cost_price * 1.5 : undefined"
:precision="2"
:step="0.01"
:controls="false"
style="width: 100%"
placeholder="请输入成本价"
/>
<div v-if="packageForm.original_cost_price" class="form-tip">
原价: ¥{{ packageForm.original_cost_price.toFixed(2) }},
最高: ¥{{ (packageForm.original_cost_price * 1.5).toFixed(2) }} (50%溢价)
</div>
</ElFormItem>
</ElForm>
@@ -245,17 +250,16 @@
<ElRow :gutter="20" v-if="form.series_id">
<ElCol :span="24">
<ElFormItem label="选择店铺" prop="shop_id">
<ElTreeSelect
<ElCascader
v-model="form.shop_id"
:data="shopTreeData"
:props="{ label: 'shop_name', value: 'id', children: 'children' }"
placeholder="请选择店铺"
:options="shopCascadeOptions"
:props="shopCascadeProps"
placeholder="请选择或搜索店铺"
style="width: 100%"
filterable
clearable
:loading="shopLoading"
check-strictly
:render-after-expand="false"
@change="handleShopChange"
/>
</ElFormItem>
</ElCol>
@@ -308,6 +312,7 @@
<ElInputNumber
v-model="pkg.cost_price"
:min="pkg.original_cost_price || 0"
:max="pkg.original_cost_price ? pkg.original_cost_price * 1.5 : undefined"
:precision="2"
:step="0.01"
:controls="false"
@@ -315,7 +320,7 @@
style="width: 150px"
/>
<span v-if="pkg.original_cost_price" class="min-cost-hint">
(成本: ¥{{ pkg.original_cost_price.toFixed(2) }})
(: ¥{{ pkg.original_cost_price.toFixed(2) }}, 最高: ¥{{ (pkg.original_cost_price * 1.5).toFixed(2) }})
</span>
</div>
<ElButton type="danger" size="small" @click="removePackage(index)"
@@ -324,7 +329,7 @@
</div>
</div>
<div class="form-tip"
>设置每个套餐的成本价单位不能低于套餐原始成本</div
>设置每个套餐的成本价单位不能低于原价不能高于原价的50%</div
>
</ElFormItem>
</template>
@@ -333,12 +338,13 @@
<!-- 第二步佣金和强制充值配置 -->
<div v-show="currentStep === 1">
<!-- 一次性佣金配置 -->
<div class="form-section-title">
<span class="title-text">一次性佣金配置</span>
</div>
<template v-if="form.enable_one_time_commission">
<div class="form-section-title">
<span class="title-text">一次性佣金配置</span>
</div>
<!-- 佣金类型和金额 - 2列布局 -->
<ElRow :gutter="20">
<!-- 佣金类型和金额 - 2列布局 -->
<ElRow :gutter="20">
<ElCol :span="12">
<ElFormItem label="佣金类型">
<div class="commission-type-display">
@@ -432,6 +438,7 @@
</div>
</ElFormItem>
</template>
</template>
<!-- 强制充值配置 -->
<div class="form-section-title">
@@ -497,12 +504,13 @@
</div>
<!-- 一次性佣金配置 -->
<div class="form-section-title">
<span class="title-text">一次性佣金配置</span>
</div>
<template v-if="form.enable_one_time_commission">
<div class="form-section-title">
<span class="title-text">一次性佣金配置</span>
</div>
<!-- 佣金类型和金额 - 2列布局 -->
<ElRow :gutter="20" v-if="form.series_id">
<!-- 佣金类型和金额 - 2列布局 -->
<ElRow :gutter="20" v-if="form.series_id">
<ElCol :span="12">
<ElFormItem label="佣金类型">
<div class="commission-type-display">
@@ -596,6 +604,7 @@
</div>
</ElFormItem>
</template>
</template>
<!-- 强制充值配置 -->
<div class="form-section-title">
@@ -808,6 +817,7 @@
package_name?: string
package_code?: string
cost_price_yuan: number
original_cost_price?: number // 原始成本价(元)
}>({
cost_price_yuan: 0
})
@@ -819,7 +829,22 @@
],
cost_price_yuan: [
{ required: true, message: '请输入成本价', trigger: 'blur' },
{ type: 'number', min: 0, message: '成本价不能小于0', trigger: 'blur' }
{
validator: (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback(new Error('请输入成本价'))
} else if (value < 0) {
callback(new Error('成本价不能小于0'))
} else if (packageForm.value.original_cost_price && value < packageForm.value.original_cost_price) {
callback(new Error(`成本价不能低于原价 ¥${packageForm.value.original_cost_price.toFixed(2)}`))
} else if (packageForm.value.original_cost_price && value > packageForm.value.original_cost_price * 1.5) {
callback(new Error(`成本价不能高于原价的50%溢价(最高:¥${(packageForm.value.original_cost_price * 1.5).toFixed(2)}`))
} else {
callback()
}
},
trigger: 'blur'
}
]
}))
@@ -832,6 +857,37 @@
const searchShopOptions = ref<ShopResponse[]>([])
const searchAllocatorShopOptions = ref<ShopResponse[]>([])
// 店铺级联选择相关
const shopCascadeOptions = ref<any[]>([])
const shopCascadeProps = {
lazy: true,
checkStrictly: true, // 允许选择任意一级
lazyLoad: async (node: any, resolve: any) => {
const { level, value } = node
const parentId = level === 0 ? undefined : value
try {
const res = await ShopService.getShopsCascade({ parent_id: parentId })
if (res.code === 0) {
const nodes = (res.data || []).map((item: any) => ({
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
resolve(nodes)
} else {
resolve([])
}
} catch (error) {
console.error('加载店铺级联数据失败:', error)
resolve([])
}
},
value: 'value',
label: 'label',
children: 'children'
}
// 搜索表单初始值
const initialSearchState = {
shop_id: undefined as number | undefined,
@@ -944,8 +1000,9 @@
series_name: '',
shop_name: '',
allocator_shop_name: '',
enable_one_time_commission: false, // 是否启用一次性佣金(从系列继承)
commission_type: 'fixed' as 'fixed' | 'tiered', // 佣金类型
one_time_commission_amount: 0, // 固定佣金金额(元)
one_time_commission_amount: undefined, // 固定佣金金额(元)
series_max_commission_amount: 0, // 系列最大佣金金额(元)
commission_tiers: [] as Array<{
operator?: '>=' | '>' | '<=' | '<' // 比较运算符
@@ -968,59 +1025,62 @@
shop_id: [{ required: true, message: '请选择店铺', trigger: 'change' }]
}
// 根据佣金类型添加验证规则
if (form.commission_type === 'fixed') {
baseRules.one_time_commission_amount = [
{ required: true, message: '请输入固定佣金金额', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback(new Error('请输入固定佣金金额'))
} else if (value < 0) {
callback(new Error('佣金金额不能小于0'))
} else if (
form.series_max_commission_amount > 0 &&
value > form.series_max_commission_amount
) {
callback(
new Error(
`佣金金额不能超过该系列最大值 ¥${form.series_max_commission_amount.toFixed(2)}`
)
)
} else {
callback()
}
},
trigger: 'blur'
}
]
} else if (form.commission_type === 'tiered') {
baseRules.commission_tiers = [
{
validator: (rule, value, callback) => {
if (!value || value.length === 0) {
callback(new Error('请至少添加一个梯度配置'))
return
}
// 验证每个梯度的佣金金额不超过最大值
for (let i = 0; i < value.length; i++) {
const tier = value[i]
if (tier.max_amount && tier.amount > tier.max_amount) {
// 只有启用一次性佣金时才添加佣金验证规则
if (form.enable_one_time_commission) {
// 根据佣金类型添加验证规则
if (form.commission_type === 'fixed') {
baseRules.one_time_commission_amount = [
{ required: true, message: '请输入固定佣金金额', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback(new Error('请输入固定佣金金额'))
} else if (value < 0) {
callback(new Error('佣金金额不能小于0'))
} else if (
form.series_max_commission_amount > 0 &&
value > form.series_max_commission_amount
) {
callback(
new Error(
`${i + 1}个梯度的佣金金额¥${tier.amount.toFixed(2)}不能超过系列配置的最大值¥${tier.max_amount.toFixed(2)}`
`佣金金额不能超过系列最大值 ¥${form.series_max_commission_amount.toFixed(2)}`
)
)
} else {
callback()
}
},
trigger: 'blur'
}
]
} else if (form.commission_type === 'tiered') {
baseRules.commission_tiers = [
{
validator: (rule, value, callback) => {
if (!value || value.length === 0) {
callback(new Error('请至少添加一个梯度配置'))
return
}
}
callback()
},
trigger: 'change'
}
]
// 验证每个梯度的佣金金额不超过最大值
for (let i = 0; i < value.length; i++) {
const tier = value[i]
if (tier.max_amount && tier.amount > tier.max_amount) {
callback(
new Error(
`${i + 1}个梯度的佣金金额¥${tier.amount.toFixed(2)}不能超过系列配置的最大值¥${tier.max_amount.toFixed(2)}`
)
)
return
}
}
callback()
},
trigger: 'change'
}
]
}
}
// 如果启用了强制充值,添加验证规则
@@ -1207,9 +1267,14 @@
return tree
}
// 店铺树节点过滤方法
const filterShopNode = (value: string, data: any) => {
if (!value) return true
return data.shop_name.toLowerCase().includes(value.toLowerCase())
}
onMounted(() => {
loadSeriesOptions()
loadShopOptions()
loadSearchSeriesOptions()
loadSearchShopOptions()
loadSearchAllocatorShopOptions()
@@ -1322,6 +1387,9 @@
// 设置系列名称
form.series_name = selectedSeries.series_name
// 获取系列是否启用一次性佣金
form.enable_one_time_commission = selectedSeries.enable_one_time_commission || false
// 从系列配置中获取佣金类型
const commissionConfig = selectedSeries.one_time_commission_config
if (commissionConfig && commissionConfig.commission_type) {
@@ -1329,14 +1397,14 @@
// 根据佣金类型初始化对应字段
if (commissionConfig.commission_type === 'fixed') {
form.one_time_commission_amount = 0
form.one_time_commission_amount = undefined
// 设置系列最大佣金金额(从分转换为元)
form.series_max_commission_amount = commissionConfig.commission_amount
? commissionConfig.commission_amount / 100
: 0
form.commission_tiers = [{ threshold: 0, amount: 0 }]
} else if (commissionConfig.commission_type === 'tiered') {
form.one_time_commission_amount = 0
form.one_time_commission_amount = undefined
form.series_max_commission_amount = 0
// 梯度配置从系列继承,包含完整字段,佣金金额也默认继承
if (commissionConfig.tiers && commissionConfig.tiers.length > 0) {
@@ -1362,7 +1430,7 @@
} else {
// 默认固定佣金
form.commission_type = 'fixed'
form.one_time_commission_amount = 0
form.one_time_commission_amount = undefined
form.series_max_commission_amount = 0
form.series_force_recharge_amount = 0
}
@@ -1374,7 +1442,7 @@
// 未选择系列,重置佣金类型
form.series_name = ''
form.commission_type = 'fixed'
form.one_time_commission_amount = 0
form.one_time_commission_amount = undefined
form.series_max_commission_amount = 0
form.series_force_recharge_amount = 0
form.commission_tiers = []
@@ -1394,6 +1462,22 @@
}
)
// 监听套餐选择,自动设置原始成本价和成本价
watch(
() => packageForm.value.package_id,
(packageId) => {
if (packageDialogType.value === 'add' && packageId) {
// 从 availablePackages 中查找选中的套餐
const selectedPackage = availablePackages.value.find((p) => p.id === packageId)
if (selectedPackage) {
const originalCostPrice = selectedPackage.cost_price ? selectedPackage.cost_price / 100 : 0
packageForm.value.original_cost_price = originalCostPrice
packageForm.value.cost_price_yuan = originalCostPrice
}
}
}
)
// 加载系列选项(用于新增对话框,默认加载10条
const loadSeriesOptions = async (seriesName?: string) => {
seriesLoading.value = true
@@ -1417,26 +1501,6 @@
}
}
// 加载店铺选项(用于新增对话框,加载所有店铺并构建树形结构)
const loadShopOptions = async () => {
shopLoading.value = true
try {
// 加载所有店铺,不分页
const res = await ShopService.getShops({
page: 1,
page_size: 10000 // 使用较大的值获取所有店铺
})
if (res.code === 0) {
shopOptions.value = res.data.items
// 构建树形结构数据
shopTreeData.value = buildTreeData(shopOptions.value)
}
} catch (error) {
console.error('加载店铺选项失败:', error)
} finally {
shopLoading.value = false
}
}
// 加载搜索栏系列选项(默认加载10条)
const loadSearchSeriesOptions = async () => {
@@ -1487,6 +1551,35 @@
}
}
// 加载店铺列表(用于新增对话框) - 改用级联查询
const loadShopListForDialog = async () => {
shopLoading.value = true
try {
const res = await ShopService.getShopsCascade({ parent_id: undefined })
if (res.code === 0) {
shopCascadeOptions.value = (res.data || []).map((item: any) => ({
value: item.id,
label: item.shop_name,
leaf: !item.has_children
}))
}
} catch (error) {
console.error('加载店铺列表失败:', error)
} finally {
shopLoading.value = false
}
}
// 处理店铺选择变化
const handleShopChange = (value: any) => {
// 级联选择器返回的是数组,取最后一个值作为 shop_id
if (Array.isArray(value) && value.length > 0) {
form.shop_id = value[value.length - 1]
} else {
form.shop_id = undefined
}
}
// 搜索系列(用于搜索栏)
const handleSearchSeries = async (query: string) => {
if (!query) {
@@ -1608,6 +1701,9 @@
dialogVisible.value = true
dialogType.value = type
// 每次打开对话框时重新加载店铺列表,确保获取最新的店铺数据
await loadShopListForDialog()
if (type === 'edit' && row) {
// 编辑模式:先调用详情接口获取完整数据
loading.value = true
@@ -1628,9 +1724,13 @@
// 获取系列配置信息
const selectedSeries = seriesOptions.value.find((s) => s.id === detail.series_id)
// 设置是否启用一次性佣金
form.enable_one_time_commission = selectedSeries?.enable_one_time_commission || false
// 设置佣金配置
if (detail.commission_type === 'fixed') {
form.one_time_commission_amount = detail.one_time_commission_amount / 100
const amount = detail.one_time_commission_amount / 100
form.one_time_commission_amount = amount > 0 ? amount : undefined
// 设置系列最大佣金金额
if (selectedSeries?.one_time_commission_config?.commission_amount) {
form.series_max_commission_amount =
@@ -1711,8 +1811,9 @@
form.series_name = ''
form.shop_name = ''
form.allocator_shop_name = ''
form.enable_one_time_commission = false
form.commission_type = 'fixed'
form.one_time_commission_amount = 0
form.one_time_commission_amount = undefined
form.series_max_commission_amount = 0
form.series_force_recharge_amount = 0
form.commission_tiers = []
@@ -1742,8 +1843,9 @@
form.series_name = ''
form.shop_name = ''
form.allocator_shop_name = ''
form.enable_one_time_commission = false
form.commission_type = 'fixed'
form.one_time_commission_amount = 0
form.one_time_commission_amount = undefined
form.series_max_commission_amount = 0
form.series_force_recharge_amount = 0
form.commission_tiers = []
@@ -1803,17 +1905,20 @@
// 构建请求数据
const data: any = {}
// 根据佣金类型携带不同参数
if (form.commission_type === 'fixed') {
// 固定模式:携带 one_time_commission_amount将元转换为分
data.one_time_commission_amount = Math.round(form.one_time_commission_amount * 100)
} else if (form.commission_type === 'tiered') {
// 梯度模式:携带 commission_tiers将元转换为分
// 注意:请求时不传 operatoroperator 是响应中从 PackageSeries 合并过来的
data.commission_tiers = form.commission_tiers.map((tier: CommissionTier) => ({
threshold: tier.threshold,
amount: Math.round(tier.amount * 100) // 将元转换为分
}))
// 只有启用一次性佣金时才传递佣金相关参数
if (form.enable_one_time_commission) {
// 根据佣金类型携带不同参数
if (form.commission_type === 'fixed') {
// 固定模式:携带 one_time_commission_amount将元转换为分
data.one_time_commission_amount = Math.round((form.one_time_commission_amount || 0) * 100)
} else if (form.commission_type === 'tiered') {
// 梯度模式:携带 commission_tiers(将元转换为分)
// 注意:请求时不传 operatoroperator 是响应中从 PackageSeries 合并过来的
data.commission_tiers = form.commission_tiers.map((tier: CommissionTier) => ({
threshold: tier.threshold,
amount: Math.round(tier.amount * 100) // 将元转换为分
}))
}
}
// 强制充值配置(无论开关状态都要传递)
@@ -1828,9 +1933,30 @@
// 可选:套餐配置(将元转换为分)
if (form.packages.length > 0) {
// 验证成本价是否在允许范围内
for (const pkg of form.packages) {
const costPrice = pkg.cost_price ?? 0
const originalCostPrice = pkg.original_cost_price ?? 0
if (originalCostPrice > 0 && costPrice > originalCostPrice * 1.5) {
ElMessage.error(
`套餐 ${getPackageName(pkg.package_id)} 的成本价不能高于原价的50%溢价(最高:¥${(originalCostPrice * 1.5).toFixed(2)}`
)
submitLoading.value = false
return
}
if (originalCostPrice > 0 && costPrice < originalCostPrice) {
ElMessage.error(
`套餐 ${getPackageName(pkg.package_id)} 的成本价不能低于原价(¥${originalCostPrice.toFixed(2)}`
)
submitLoading.value = false
return
}
}
data.packages = form.packages.map((pkg: any) => ({
package_id: pkg.package_id,
cost_price: Math.round(pkg.cost_price * 100) // 将元转换为分
cost_price: Math.round((pkg.cost_price ?? 0) * 100) // 将元转换为分
}))
}
@@ -1912,11 +2038,17 @@
// 显示编辑套餐对话框
const showEditPackageDialog = (row: GrantPackageInfo) => {
packageDialogType.value = 'edit'
// 从 packageOptions 中查找原始成本价
const pkgOption = packageOptions.value.find((p) => p.id === row.package_id)
const originalCostPrice = pkgOption?.cost_price ? pkgOption.cost_price / 100 : 0
packageForm.value = {
package_id: row.package_id,
package_name: row.package_name,
package_code: row.package_code,
cost_price_yuan: row.cost_price / 100
cost_price_yuan: row.cost_price / 100,
original_cost_price: originalCostPrice
}
packageDialogVisible.value = true