This commit is contained in:
@@ -405,4 +405,30 @@ export class CardService extends BaseService {
|
||||
static deactivateIotCard(id: number): Promise<BaseResponse> {
|
||||
return this.patch<BaseResponse>(`/api/admin/iot-cards/${id}/deactivate`, {})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取套餐流量详单(每日流量记录)
|
||||
* @param packageUsageId 套餐使用记录ID
|
||||
*/
|
||||
static getPackageDailyRecords(packageUsageId: number): Promise<BaseResponse<{
|
||||
package_name: string
|
||||
package_usage_id: number
|
||||
total_usage_mb: number
|
||||
records: Array<{
|
||||
date: string
|
||||
daily_usage_mb: number
|
||||
cumulative_usage_mb: number
|
||||
}>
|
||||
}>> {
|
||||
return this.get<BaseResponse<{
|
||||
package_name: string
|
||||
package_usage_id: number
|
||||
total_usage_mb: number
|
||||
records: Array<{
|
||||
date: string
|
||||
daily_usage_mb: number
|
||||
cumulative_usage_mb: number
|
||||
}>
|
||||
}>>(`/api/admin/package-usage/${packageUsageId}/daily-records`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
<template>
|
||||
<div class="single-card-page">
|
||||
<!-- 占位符,用于在卡片固定时保持布局 -->
|
||||
<div v-if="isSearchCardFixed" ref="searchCardPlaceholder" class="search-card-placeholder"></div>
|
||||
|
||||
<!-- ICCID查询区域 -->
|
||||
<!-- 资产查询区域 -->
|
||||
<ElCard
|
||||
ref="searchCardRef"
|
||||
shadow="never"
|
||||
class="search-card"
|
||||
:class="{ 'is-fixed': isSearchCardFixed }"
|
||||
:style="fixedCardStyle"
|
||||
>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
@@ -488,12 +482,19 @@
|
||||
class="package-table"
|
||||
border
|
||||
>
|
||||
<ElTableColumn
|
||||
prop="package_name"
|
||||
label="套餐名称"
|
||||
min-width="150"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<ElTableColumn label="套餐名称" min-width="150">
|
||||
<template #default="scope">
|
||||
<ElButton
|
||||
type="primary"
|
||||
link
|
||||
class="package-name-link"
|
||||
@click="handleShowDailyRecords(scope.row)"
|
||||
v-permission="'package-usage:daily-records:view'"
|
||||
>
|
||||
{{ scope.row.package_name }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="package_type" label="套餐类型" width="100">
|
||||
<template #default="scope">
|
||||
<ElTag
|
||||
@@ -876,6 +877,93 @@
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 流量详单对话框 -->
|
||||
<ElDialog
|
||||
v-model="dailyRecordsDialogVisible"
|
||||
title="套餐流量详单"
|
||||
width="900px"
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-if="dailyRecordsData">
|
||||
<!-- 套餐信息摘要 -->
|
||||
<ElDescriptions :column="2" border style="margin-bottom: 20px">
|
||||
<ElDescriptionsItem label="套餐名称">
|
||||
{{ dailyRecordsData.package_name }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐使用记录ID">
|
||||
{{ dailyRecordsData.package_usage_id }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="总使用流量" :span="2">
|
||||
{{ formatDataSize(dailyRecordsData.total_usage_mb) }}
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
|
||||
<!-- 筛选器 -->
|
||||
<div style="margin-bottom: 16px; display: flex; align-items: center; gap: 12px">
|
||||
<span style="font-weight: 500">筛选方式:</span>
|
||||
<ElRadioGroup v-model="dateFilterType" @change="handleDateFilterChange">
|
||||
<ElRadio value="all">全部</ElRadio>
|
||||
<ElRadio value="day">按日</ElRadio>
|
||||
<ElRadio value="month">按月</ElRadio>
|
||||
</ElRadioGroup>
|
||||
<ElDatePicker
|
||||
v-if="dateFilterType === 'day'"
|
||||
v-model="selectedDate"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
clearable
|
||||
@change="filterRecordsByDate"
|
||||
style="width: 200px"
|
||||
/>
|
||||
<ElDatePicker
|
||||
v-if="dateFilterType === 'month'"
|
||||
v-model="selectedMonth"
|
||||
type="month"
|
||||
placeholder="选择月份"
|
||||
format="YYYY-MM"
|
||||
value-format="YYYY-MM"
|
||||
clearable
|
||||
@change="filterRecordsByMonth"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 每日流量记录表格 -->
|
||||
<ElTable
|
||||
:data="filteredRecords"
|
||||
border
|
||||
stripe
|
||||
max-height="400"
|
||||
v-loading="dailyRecordsLoading"
|
||||
>
|
||||
<ElTableColumn prop="date" label="日期" width="120" align="center" />
|
||||
<ElTableColumn label="当日使用流量" width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ formatDataSize(row.daily_usage_mb) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="累计流量" width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ formatDataSize(row.cumulative_usage_mb) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="使用进度" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<ElProgress
|
||||
:percentage="getUsageProgress(row.cumulative_usage_mb, dailyRecordsData.total_usage_mb)"
|
||||
:color="getProgressColorByPercentage(getUsageProgress(row.cumulative_usage_mb, dailyRecordsData.total_usage_mb))"
|
||||
/>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
</div>
|
||||
<div v-else-if="!dailyRecordsLoading" style="text-align: center; padding: 40px 0">
|
||||
<ElEmpty description="暂无流量详单数据" />
|
||||
</div>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -988,12 +1076,14 @@
|
||||
]
|
||||
})
|
||||
|
||||
const isSearchCardFixed = ref(false)
|
||||
const searchCardRef = ref<any>(null)
|
||||
const searchCardPlaceholder = ref<HTMLElement | null>(null)
|
||||
const cardOriginalTop = ref(0)
|
||||
const cardLeft = ref(0)
|
||||
const cardWidth = ref(0)
|
||||
// 流量详单相关
|
||||
const dailyRecordsDialogVisible = ref(false)
|
||||
const dailyRecordsLoading = ref(false)
|
||||
const dailyRecordsData = ref<any>(null)
|
||||
const dateFilterType = ref<'all' | 'day' | 'month'>('all')
|
||||
const selectedDate = ref<string>('')
|
||||
const selectedMonth = ref<string>('')
|
||||
const filteredRecords = ref<any[]>([])
|
||||
|
||||
// ICCID搜索相关
|
||||
const searchIccid = ref('')
|
||||
@@ -1401,57 +1491,6 @@
|
||||
// 页面初始化 - 检查URL参数自动加载
|
||||
onMounted(() => {
|
||||
autoLoadFromQuery()
|
||||
// 初始化位置信息
|
||||
nextTick(() => {
|
||||
updateCardPosition()
|
||||
})
|
||||
window.addEventListener('scroll', handleScroll, true)
|
||||
window.addEventListener('resize', updateCardPosition)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', handleScroll, true)
|
||||
window.removeEventListener('resize', updateCardPosition)
|
||||
})
|
||||
|
||||
// 更新卡片位置信息
|
||||
const updateCardPosition = () => {
|
||||
if (searchCardRef.value && searchCardRef.value.$el) {
|
||||
const rect = searchCardRef.value.$el.getBoundingClientRect()
|
||||
cardOriginalTop.value = rect.top + window.scrollY
|
||||
cardLeft.value = rect.left + window.scrollX
|
||||
cardWidth.value = rect.width
|
||||
}
|
||||
}
|
||||
|
||||
// 处理滚动事件
|
||||
const handleScroll = () => {
|
||||
if (!searchCardRef.value) return
|
||||
|
||||
const scrollTop = window.scrollY || window.pageYOffset || document.documentElement.scrollTop
|
||||
|
||||
// 判断是否应该固定:当滚动超过卡片原始位置时
|
||||
const shouldBeFixed = scrollTop > cardOriginalTop.value - 20
|
||||
|
||||
// 只在状态变化时更新
|
||||
if (shouldBeFixed !== isSearchCardFixed.value) {
|
||||
if (shouldBeFixed && !isSearchCardFixed.value) {
|
||||
// 在变成固定之前,更新位置信息
|
||||
updateCardPosition()
|
||||
}
|
||||
isSearchCardFixed.value = shouldBeFixed
|
||||
}
|
||||
}
|
||||
|
||||
// 计算固定时的样式
|
||||
const fixedCardStyle = computed(() => {
|
||||
if (isSearchCardFixed.value && cardWidth.value > 0) {
|
||||
return {
|
||||
left: `${cardLeft.value}px`,
|
||||
width: `${cardWidth.value}px`
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
// 监听路由查询参数变化
|
||||
@@ -2211,31 +2250,98 @@
|
||||
manualDeactivateDeviceLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 显示流量详单
|
||||
const handleShowDailyRecords = async (packageRow: any) => {
|
||||
dailyRecordsDialogVisible.value = true
|
||||
dailyRecordsLoading.value = true
|
||||
dailyRecordsData.value = null
|
||||
dateFilterType.value = 'all'
|
||||
selectedDate.value = ''
|
||||
selectedMonth.value = ''
|
||||
|
||||
try {
|
||||
const res = await CardService.getPackageDailyRecords(packageRow.package_usage_id)
|
||||
if (res.code === 0 && res.data) {
|
||||
dailyRecordsData.value = res.data
|
||||
filteredRecords.value = res.data.records || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载流量详单失败:', error)
|
||||
ElMessage.error('加载流量详单失败')
|
||||
} finally {
|
||||
dailyRecordsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理筛选方式变化
|
||||
const handleDateFilterChange = () => {
|
||||
selectedDate.value = ''
|
||||
selectedMonth.value = ''
|
||||
filteredRecords.value = dailyRecordsData.value?.records || []
|
||||
}
|
||||
|
||||
// 按日期筛选
|
||||
const filterRecordsByDate = () => {
|
||||
if (!dailyRecordsData.value?.records) return
|
||||
|
||||
if (!selectedDate.value) {
|
||||
filteredRecords.value = dailyRecordsData.value.records
|
||||
return
|
||||
}
|
||||
|
||||
filteredRecords.value = dailyRecordsData.value.records.filter((record: any) => {
|
||||
return record.date === selectedDate.value
|
||||
})
|
||||
}
|
||||
|
||||
// 按月份筛选
|
||||
const filterRecordsByMonth = () => {
|
||||
if (!dailyRecordsData.value?.records) return
|
||||
|
||||
if (!selectedMonth.value) {
|
||||
filteredRecords.value = dailyRecordsData.value.records
|
||||
return
|
||||
}
|
||||
|
||||
filteredRecords.value = dailyRecordsData.value.records.filter((record: any) => {
|
||||
return record.date.startsWith(selectedMonth.value)
|
||||
})
|
||||
}
|
||||
|
||||
// 计算使用进度百分比
|
||||
const getUsageProgress = (cumulative: number, total: number): number => {
|
||||
if (!total || total <= 0) return 0
|
||||
const percentage = (cumulative / total) * 100
|
||||
return Math.min(Math.round(percentage * 100) / 100, 100)
|
||||
}
|
||||
|
||||
// 根据百分比获取进度条颜色
|
||||
const getProgressColorByPercentage = (percentage: number): string => {
|
||||
if (percentage < 50) return '#67c23a'
|
||||
if (percentage < 80) return '#e6a23c'
|
||||
return '#f56c6c'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 套餐名称链接样式
|
||||
.package-name-link {
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.single-card-page {
|
||||
padding: 20px;
|
||||
|
||||
// 占位符
|
||||
.search-card-placeholder {
|
||||
height: 140px; // 大约等于搜索卡片的高度
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
// ICCID搜索卡片
|
||||
// 资产查询卡片
|
||||
.search-card {
|
||||
margin-bottom: 20px;
|
||||
overflow: visible;
|
||||
background: var(--el-bg-color, #fff);
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
&.is-fixed {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
z-index: 100;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
:deep(.el-card__header) {
|
||||
padding: 16px 20px;
|
||||
|
||||
@@ -375,11 +375,14 @@
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: 'ID', prop: 'id' },
|
||||
{ label: '配置名称', prop: 'name' },
|
||||
{ label: '配置描述', prop: 'description' },
|
||||
{ label: '支付渠道类型', prop: 'provider_type' },
|
||||
{ label: '激活状态', prop: 'is_active' },
|
||||
{ label: '商户号', prop: 'merchant_id' },
|
||||
{ label: '小程序AppID', prop: 'miniapp_app_id' },
|
||||
{ label: '公众号AppID', prop: 'oa_app_id' },
|
||||
{ label: '支付回调地址', prop: 'notify_url' },
|
||||
{ label: '创建时间', prop: 'created_at' },
|
||||
{ label: '更新时间', prop: 'updated_at' }
|
||||
]
|
||||
@@ -479,17 +482,23 @@
|
||||
return '-'
|
||||
}
|
||||
|
||||
// 获取支付回调地址
|
||||
const getNotifyUrl = (row: WechatConfig): string => {
|
||||
if (row.provider_type === 'wechat') {
|
||||
return row.wx_notify_url || '-'
|
||||
}
|
||||
if (row.provider_type === 'fuiou') {
|
||||
return row.fy_notify_url || '-'
|
||||
}
|
||||
return '-'
|
||||
}
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'id',
|
||||
label: 'ID',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
prop: 'name',
|
||||
label: '配置名称',
|
||||
minWidth: 180,
|
||||
minWidth: 160,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: WechatConfig) => {
|
||||
return h(
|
||||
@@ -505,6 +514,13 @@
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'description',
|
||||
label: '配置描述',
|
||||
minWidth: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: WechatConfig) => row.description || '-'
|
||||
},
|
||||
{
|
||||
prop: 'provider_type',
|
||||
label: '支付渠道类型',
|
||||
@@ -529,8 +545,30 @@
|
||||
prop: 'merchant_id',
|
||||
label: '商户号',
|
||||
width: 150,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: WechatConfig) => getMerchantId(row)
|
||||
},
|
||||
{
|
||||
prop: 'miniapp_app_id',
|
||||
label: '小程序AppID',
|
||||
width: 160,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: WechatConfig) => row.miniapp_app_id || '-'
|
||||
},
|
||||
{
|
||||
prop: 'oa_app_id',
|
||||
label: '公众号AppID',
|
||||
width: 160,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: WechatConfig) => row.oa_app_id || '-'
|
||||
},
|
||||
{
|
||||
prop: 'notify_url',
|
||||
label: '支付回调地址',
|
||||
minWidth: 200,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: WechatConfig) => getNotifyUrl(row)
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
|
||||
Reference in New Issue
Block a user