This commit is contained in:
@@ -405,4 +405,30 @@ export class CardService extends BaseService {
|
|||||||
static deactivateIotCard(id: number): Promise<BaseResponse> {
|
static deactivateIotCard(id: number): Promise<BaseResponse> {
|
||||||
return this.patch<BaseResponse>(`/api/admin/iot-cards/${id}/deactivate`, {})
|
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>
|
<template>
|
||||||
<div class="single-card-page">
|
<div class="single-card-page">
|
||||||
<!-- 占位符,用于在卡片固定时保持布局 -->
|
<!-- 资产查询区域 -->
|
||||||
<div v-if="isSearchCardFixed" ref="searchCardPlaceholder" class="search-card-placeholder"></div>
|
|
||||||
|
|
||||||
<!-- ICCID查询区域 -->
|
|
||||||
<ElCard
|
<ElCard
|
||||||
ref="searchCardRef"
|
|
||||||
shadow="never"
|
shadow="never"
|
||||||
class="search-card"
|
class="search-card"
|
||||||
:class="{ 'is-fixed': isSearchCardFixed }"
|
|
||||||
:style="fixedCardStyle"
|
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@@ -488,12 +482,19 @@
|
|||||||
class="package-table"
|
class="package-table"
|
||||||
border
|
border
|
||||||
>
|
>
|
||||||
<ElTableColumn
|
<ElTableColumn label="套餐名称" min-width="150">
|
||||||
prop="package_name"
|
<template #default="scope">
|
||||||
label="套餐名称"
|
<ElButton
|
||||||
min-width="150"
|
type="primary"
|
||||||
show-overflow-tooltip
|
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">
|
<ElTableColumn prop="package_type" label="套餐类型" width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<ElTag
|
<ElTag
|
||||||
@@ -876,6 +877,93 @@
|
|||||||
</ElButton>
|
</ElButton>
|
||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -988,12 +1076,14 @@
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const isSearchCardFixed = ref(false)
|
// 流量详单相关
|
||||||
const searchCardRef = ref<any>(null)
|
const dailyRecordsDialogVisible = ref(false)
|
||||||
const searchCardPlaceholder = ref<HTMLElement | null>(null)
|
const dailyRecordsLoading = ref(false)
|
||||||
const cardOriginalTop = ref(0)
|
const dailyRecordsData = ref<any>(null)
|
||||||
const cardLeft = ref(0)
|
const dateFilterType = ref<'all' | 'day' | 'month'>('all')
|
||||||
const cardWidth = ref(0)
|
const selectedDate = ref<string>('')
|
||||||
|
const selectedMonth = ref<string>('')
|
||||||
|
const filteredRecords = ref<any[]>([])
|
||||||
|
|
||||||
// ICCID搜索相关
|
// ICCID搜索相关
|
||||||
const searchIccid = ref('')
|
const searchIccid = ref('')
|
||||||
@@ -1401,57 +1491,6 @@
|
|||||||
// 页面初始化 - 检查URL参数自动加载
|
// 页面初始化 - 检查URL参数自动加载
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
autoLoadFromQuery()
|
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
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
// 套餐名称链接样式
|
||||||
|
.package-name-link {
|
||||||
|
text-decoration: underline;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.single-card-page {
|
.single-card-page {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
||||||
// 占位符
|
// 资产查询卡片
|
||||||
.search-card-placeholder {
|
|
||||||
height: 140px; // 大约等于搜索卡片的高度
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ICCID搜索卡片
|
|
||||||
.search-card {
|
.search-card {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
background: var(--el-bg-color, #fff);
|
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) {
|
:deep(.el-card__header) {
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
|
|||||||
@@ -375,11 +375,14 @@
|
|||||||
|
|
||||||
// 列配置
|
// 列配置
|
||||||
const columnOptions = [
|
const columnOptions = [
|
||||||
{ label: 'ID', prop: 'id' },
|
|
||||||
{ label: '配置名称', prop: 'name' },
|
{ label: '配置名称', prop: 'name' },
|
||||||
|
{ label: '配置描述', prop: 'description' },
|
||||||
{ label: '支付渠道类型', prop: 'provider_type' },
|
{ label: '支付渠道类型', prop: 'provider_type' },
|
||||||
{ label: '激活状态', prop: 'is_active' },
|
{ label: '激活状态', prop: 'is_active' },
|
||||||
{ label: '商户号', prop: 'merchant_id' },
|
{ 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: 'created_at' },
|
||||||
{ label: '更新时间', prop: 'updated_at' }
|
{ label: '更新时间', prop: 'updated_at' }
|
||||||
]
|
]
|
||||||
@@ -479,17 +482,23 @@
|
|||||||
return '-'
|
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(() => [
|
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||||
{
|
|
||||||
prop: 'id',
|
|
||||||
label: 'ID',
|
|
||||||
width: 80
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
prop: 'name',
|
prop: 'name',
|
||||||
label: '配置名称',
|
label: '配置名称',
|
||||||
minWidth: 180,
|
minWidth: 160,
|
||||||
showOverflowTooltip: true,
|
showOverflowTooltip: true,
|
||||||
formatter: (row: WechatConfig) => {
|
formatter: (row: WechatConfig) => {
|
||||||
return h(
|
return h(
|
||||||
@@ -505,6 +514,13 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
prop: 'description',
|
||||||
|
label: '配置描述',
|
||||||
|
minWidth: 180,
|
||||||
|
showOverflowTooltip: true,
|
||||||
|
formatter: (row: WechatConfig) => row.description || '-'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
prop: 'provider_type',
|
prop: 'provider_type',
|
||||||
label: '支付渠道类型',
|
label: '支付渠道类型',
|
||||||
@@ -529,8 +545,30 @@
|
|||||||
prop: 'merchant_id',
|
prop: 'merchant_id',
|
||||||
label: '商户号',
|
label: '商户号',
|
||||||
width: 150,
|
width: 150,
|
||||||
|
showOverflowTooltip: true,
|
||||||
formatter: (row: WechatConfig) => getMerchantId(row)
|
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',
|
prop: 'created_at',
|
||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
|
|||||||
Reference in New Issue
Block a user