fix: 真流量虚流量权限
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m42s

This commit is contained in:
sexygoat
2026-05-14 10:52:11 +08:00
parent 1690252740
commit 38cf7d12e9
29 changed files with 859 additions and 198 deletions

View File

@@ -34,7 +34,7 @@
<!-- 操作按钮组 -->
<div v-if="hasCardInfo" class="operation-button-group">
<ElButton @click="handleRefresh" type="primary" :icon="Refresh" :disabled="refreshDisabled">
刷新
同步
</ElButton>
</div>
</div>

View File

@@ -31,12 +31,12 @@
<template v-else-if="currentPackage">
<!-- 流量使用情况 - 横向进度条卡片 -->
<div class="flow-progress-card">
<div v-if="shouldShowTrafficCard" class="flow-progress-card">
<!-- 管理员视角始终显示真流量和虚流量 -->
<template v-if="isAdminOrPlatform">
<div class="flow-stats-container">
<!-- 真流量 -->
<div class="flow-stat-section">
<div v-if="canViewCurrentPackageRealUsage" class="flow-stat-section">
<div class="flow-stat-label">真流量使用</div>
<div class="flow-stat-row">
<span class="stat-item">
@@ -79,7 +79,7 @@
/>
</div>
<!-- 虚流量 -->
<div class="flow-stat-section">
<div v-if="canViewCurrentPackageVirtualUsage" class="flow-stat-section">
<div class="flow-stat-label">虚流量使用</div>
<div class="flow-stat-row">
<span class="stat-item">
@@ -211,6 +211,9 @@
<ElDescriptionsItem v-if="currentPackage.duration_days" label="套餐时长">
{{ currentPackage.duration_days }}
</ElDescriptionsItem>
<ElDescriptionsItem label="到期计时基准">
{{ getExpiryBaseLabel(currentPackage.expiry_base) }}
</ElDescriptionsItem>
</ElDescriptions>
</template>
@@ -230,6 +233,7 @@
ElAlert
} from 'element-plus'
import { useAssetFormatters } from '../composables/useAssetFormatters'
import { useAuth } from '@/composables/useAuth'
import { useUserStore } from '@/store/modules/user'
import { storeToRefs } from 'pinia'
import type { AssetPackageUsageRecord } from '@/types/api'
@@ -238,17 +242,29 @@
const {
formatAmount,
formatDataSize,
getExpiryBaseLabel,
getUsageProgress,
getProgressColorByPercentage,
formatProgressPercentage,
getBreakpointMarkerStyle
} = useAssetFormatters()
const { hasAuth } = useAuth()
const userStore = useUserStore()
const { info: userInfo } = storeToRefs(userStore)
const isAdminOrPlatform = computed(
() => userInfo.value.user_type === 1 || userInfo.value.user_type === 2
)
const canViewCurrentPackageRealUsage = computed(() =>
hasAuth('asset_info:view_current_package_real_usage')
)
const canViewCurrentPackageVirtualUsage = computed(() =>
hasAuth('asset_info:view_current_package_virtual_usage')
)
const shouldShowTrafficCard = computed(() => {
if (!isAdminOrPlatform.value) return true
return canViewCurrentPackageRealUsage.value || canViewCurrentPackageVirtualUsage.value
})
interface Props {
currentPackage: AssetPackageUsageRecord | null

View File

@@ -63,8 +63,8 @@
<ElTableColumn label="流量" min-width="450">
<template #default="scope">
<div class="traffic-progress">
<template v-if="isAdminOrPlatform">
<div class="progress-text">
<template v-if="isAdminOrPlatform && shouldShowAdminTrafficBreakdown">
<div v-if="canViewCurrentPackageRealUsage" class="progress-text">
<span class="used"
>已使用真流量: {{ formatDataSize(scope.row.real_used_mb || 0) }}</span
>
@@ -81,6 +81,7 @@
>
</div>
<ElProgress
v-if="canViewCurrentPackageRealUsage"
style="margin-top: 4px"
:percentage="
getUsageProgress(scope.row.real_used_mb || 0, scope.row.real_total_mb || 0)
@@ -91,7 +92,11 @@
)
"
/>
<div class="progress-text" style="margin-top: 8px">
<div
v-if="canViewCurrentPackageVirtualUsage"
class="progress-text"
:style="{ marginTop: canViewCurrentPackageRealUsage ? '8px' : '0' }"
>
<span class="used"
>已使用虚流量: {{ formatDataSize(scope.row.virtual_used_mb || 0) }}</span
>
@@ -99,7 +104,7 @@
>停机阈值: {{ formatDataSize(scope.row.virtual_total_mb || 0) }}</span
>
</div>
<div class="progress-wrapper">
<div v-if="canViewCurrentPackageVirtualUsage" class="progress-wrapper">
<div class="progress-track">
<ElProgress
style="margin-top: 4px"
@@ -142,7 +147,7 @@
</span>
</div>
</template>
<template v-else>
<template v-else-if="!isAdminOrPlatform">
<div class="progress-text">
<span class="used"
>已使用: {{ formatDataSize(getDisplayedUsedMb(scope.row)) }}</span
@@ -271,9 +276,19 @@
const { hasAuth } = useAuth()
const userStore = useUserStore()
const { info: userInfo } = storeToRefs(userStore)
const canViewCurrentPackageRealUsage = computed(() =>
hasAuth('asset_info:view_current_package_real_usage')
)
const canViewCurrentPackageVirtualUsage = computed(() =>
hasAuth('asset_info:view_current_package_virtual_usage')
)
const isAdminOrPlatform = computed(
() => userInfo.value.user_type === 1 || userInfo.value.user_type === 2
)
const shouldShowAdminTrafficBreakdown = computed(() => {
if (!isAdminOrPlatform.value) return true
return canViewCurrentPackageRealUsage.value || canViewCurrentPackageVirtualUsage.value
})
// 退款申请对话框
const createRefundDialogVisible = ref(false)

View File

@@ -196,6 +196,15 @@ export function useAssetFormatters() {
return statusMap[status] || 'info'
}
// 鑾峰彇鍒版湡璁℃椂鍩哄噯鏂囨
const getExpiryBaseLabel = (expiryBase?: string | null) => {
const map: Record<string, string> = {
from_activation: '实名激活时起算',
from_purchase: '购买时起算'
}
return map[expiryBase || ''] || '--'
}
// 计算使用进度百分比
const getUsageProgress = (cumulative: number, total: number): number => {
if (!total || total <= 0) return 0
@@ -255,6 +264,7 @@ export function useAssetFormatters() {
getSwitchModeName,
getTransactionTypeTag,
getPaymentStatusType,
getExpiryBaseLabel,
getUsageProgress,
getProgressColorByPercentage,
formatProgressPercentage,

View File

@@ -242,6 +242,7 @@ export function useAssetInfo() {
reduction_pct: pkg.reduction_pct || 0,
start_time: pkg.activated_at || '',
expire_time: pkg.expires_at || '',
expiry_base: pkg.expiry_base ?? null,
status: pkg.status || 0,
status_name: pkg.status_name,
duration_days: undefined,

View File

@@ -823,9 +823,29 @@
}
}
// 套餐系列搜索相关
// 搜索栏远程选项
const shopOptions = ref<any[]>([])
const searchSeriesOptions = ref<any[]>([])
// 搜索店铺(用于搜索栏)
const handleSearchShops = async (query: string) => {
try {
const params: any = {
page: 1,
page_size: 20
}
if (query) {
params.shop_name = query
}
const res = await ShopService.getShops(params)
if (res.code === 0) {
shopOptions.value = res.data.items || []
}
} catch (error) {
console.error('搜索店铺失败:', error)
}
}
// 搜索套餐系列(用于搜索栏)
const handleSearchSeries = async (query: string) => {
try {
@@ -957,6 +977,7 @@
msisdn: string
virtual_no: string
device_virtual_no: string
shop_id: undefined | number
series_id: undefined | number
is_distributed: undefined | number
is_standalone: undefined | boolean
@@ -968,6 +989,7 @@
msisdn: '',
virtual_no: '',
device_virtual_no: '',
shop_id: undefined,
series_id: undefined,
is_distributed: undefined,
is_standalone: undefined,
@@ -1144,6 +1166,24 @@
placeholder: '请输入绑定设备虚拟号'
}
},
{
label: '店铺名称',
prop: 'shop_id',
type: 'select',
config: {
clearable: true,
filterable: true,
remote: true,
remoteMethod: handleSearchShops,
loading: false,
placeholder: '请选择或搜索店铺名称'
},
options: () =>
shopOptions.value.map((shop) => ({
label: shop.shop_name,
value: shop.id
}))
},
{
label: '套餐系列',
prop: 'series_id',