feat: 套餐分层级
This commit is contained in:
@@ -104,7 +104,8 @@ export class AssetService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询该资产所有套餐记录,含虚流量换算字段(分页)
|
* 查询该资产的历史套餐关系组(主套餐—加油包,分页)
|
||||||
|
* total 仅统计顶层关系组,子项由后端随父项一起返回。
|
||||||
* GET /api/admin/assets/:identifier/packages?page=1&page_size=50
|
* GET /api/admin/assets/:identifier/packages?page=1&page_size=50
|
||||||
* @param identifier 资产标识符(ICCID 或 VirtualNo)
|
* @param identifier 资产标识符(ICCID 或 VirtualNo)
|
||||||
* @param params 查询参数(可选分页参数)
|
* @param params 查询参数(可选分页参数)
|
||||||
@@ -115,12 +116,7 @@ export class AssetService extends BaseService {
|
|||||||
): Promise<BaseResponse<AssetPackageListResponse>> {
|
): Promise<BaseResponse<AssetPackageListResponse>> {
|
||||||
return this.get<BaseResponse<AssetPackageListResponse>>(
|
return this.get<BaseResponse<AssetPackageListResponse>>(
|
||||||
`/api/admin/assets/${identifier}/packages`,
|
`/api/admin/assets/${identifier}/packages`,
|
||||||
params,
|
params
|
||||||
{
|
|
||||||
requestOptions: {
|
|
||||||
show404Error: false // 404时不显示错误提示
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ export enum PackageUsageStatus {
|
|||||||
// 套餐类型
|
// 套餐类型
|
||||||
export type PackageType = 'formal' | 'addon'
|
export type PackageType = 'formal' | 'addon'
|
||||||
|
|
||||||
|
// 套餐关系状态(历史套餐列表)
|
||||||
|
export type AssetPackageRelationshipStatus = 'master_missing'
|
||||||
|
|
||||||
// 套餐使用类型
|
// 套餐使用类型
|
||||||
export type UsageType = 'single_card' | 'device'
|
export type UsageType = 'single_card' | 'device'
|
||||||
|
|
||||||
@@ -332,7 +335,7 @@ export type AssetRefreshResponse = AssetRealtimeStatusResponse
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 资产套餐使用记录
|
* 资产套餐使用记录
|
||||||
* 对应接口:GET /api/admin/assets/:asset_type/:id/packages
|
* 对应接口:GET /api/admin/assets/:identifier/packages
|
||||||
*/
|
*/
|
||||||
export interface AssetPackageUsageRecord {
|
export interface AssetPackageUsageRecord {
|
||||||
id?: number // 兼容前端当前映射字段
|
id?: number // 兼容前端当前映射字段
|
||||||
@@ -353,31 +356,34 @@ export interface AssetPackageUsageRecord {
|
|||||||
virtual_ratio?: number // 虚流量比例(real/virtual)
|
virtual_ratio?: number // 虚流量比例(real/virtual)
|
||||||
reduction_pct?: number // 展示增幅比例(小数,如 0.428571)
|
reduction_pct?: number // 展示增幅比例(小数,如 0.428571)
|
||||||
enable_virtual_data?: boolean // 是否启用虚流量
|
enable_virtual_data?: boolean // 是否启用虚流量
|
||||||
paid_amount?: number // 实际支付金额(分)
|
paid_amount?: number | null // 实际支付金额(分,仅平台账号可见)
|
||||||
retail_amount?: number // 零售价(分)
|
retail_amount?: number | null // 零售价(分)
|
||||||
package_price?: number // 套餐价格(分)
|
package_price?: number // 套餐价格(分)
|
||||||
start_time?: string // 开始时间(兼容前端现有映射字段)
|
start_time?: string // 开始时间(兼容前端现有映射字段)
|
||||||
expire_time?: string // 到期时间(兼容前端现有映射字段)
|
expire_time?: string // 到期时间(兼容前端现有映射字段)
|
||||||
duration_days?: number // 套餐时长(兼容前端现有映射字段)
|
duration_days?: number // 套餐时长(兼容前端现有映射字段)
|
||||||
activated_at?: string // 激活时间
|
activated_at?: string // 激活时间
|
||||||
expires_at?: string // 到期时间
|
expires_at?: string // 到期时间
|
||||||
master_usage_id?: number | null // 主套餐 ID(加油包时有值)
|
master_usage_id?: number | null // 主套餐使用记录 ID(加油包时有值)
|
||||||
|
relationship_status?: AssetPackageRelationshipStatus // 关系状态;master_missing 表示主套餐缺失
|
||||||
|
relationship_status_name?: string // 关系异常状态名称
|
||||||
|
expand_by_default?: boolean // 是否默认展开该主套餐下的加油包
|
||||||
|
children?: AssetPackageUsageRecord[] // 归属于该主套餐的加油包,不单独分页
|
||||||
priority?: number // 优先级
|
priority?: number // 优先级
|
||||||
created_at?: string // 创建时间
|
created_at?: string // 创建时间
|
||||||
order_id?: number // 订单ID
|
order_id?: number // 订单ID
|
||||||
refund_id?: number // 退款ID(有退款时才有值)
|
refund_id?: number // 退款ID(有退款时才有值)
|
||||||
|
refund_no?: string // 退款单号快照
|
||||||
|
expiry_base?: ExpiryBase | null // 到期计时基准
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资产套餐列表分页响应
|
* 资产套餐列表分页响应
|
||||||
* 对应接口:GET /api/admin/assets/:asset_type/:id/packages?page=1&page_size=50
|
* 每个 items 元素为一个顶层主套餐关系组,total 只统计顶层组。
|
||||||
|
* 对应接口:GET /api/admin/assets/:identifier/packages?page=1&page_size=50
|
||||||
*/
|
*/
|
||||||
export interface AssetPackageUsageRecord {
|
|
||||||
expiry_base?: ExpiryBase | null // 到期计时基准
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AssetPackageListResponse {
|
export interface AssetPackageListResponse {
|
||||||
total: number // 总记录数
|
total: number // 顶层主套餐关系组总数
|
||||||
page: number // 当前页码
|
page: number // 当前页码
|
||||||
page_size: number // 每页数量
|
page_size: number // 每页数量
|
||||||
items: AssetPackageUsageRecord[] // 套餐记录列表
|
items: AssetPackageUsageRecord[] // 套餐记录列表
|
||||||
@@ -389,6 +395,7 @@ export interface AssetPackageListResponse {
|
|||||||
export interface AssetPackageParams {
|
export interface AssetPackageParams {
|
||||||
page?: number // 页码,默认1
|
page?: number // 页码,默认1
|
||||||
page_size?: number // 每页数量,默认50
|
page_size?: number // 每页数量,默认50
|
||||||
|
status?: PackageUsageStatus | null // 按同一条使用记录筛选
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateAssetPackageUsedDataRequest {
|
export interface UpdateAssetPackageUsedDataRequest {
|
||||||
|
|||||||
@@ -29,8 +29,19 @@
|
|||||||
</template>
|
</template>
|
||||||
</ElEmpty>
|
</ElEmpty>
|
||||||
<div v-else-if="packageList && packageList.length > 0" class="table-scroll-container">
|
<div v-else-if="packageList && packageList.length > 0" class="table-scroll-container">
|
||||||
<ElTable :data="packageList" class="package-table" border>
|
<ElTable
|
||||||
<ElTableColumn label="订单号" width="240">
|
:data="packageList"
|
||||||
|
:expand-row-keys="defaultExpandedPackageUsageIds"
|
||||||
|
:tree-props="{ children: 'children' }"
|
||||||
|
row-key="package_usage_id"
|
||||||
|
class="package-table"
|
||||||
|
border
|
||||||
|
>
|
||||||
|
<ElTableColumn
|
||||||
|
label="订单号"
|
||||||
|
:width="orderNoColumnWidth"
|
||||||
|
class-name="package-order-column"
|
||||||
|
>
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="order-no-cell">
|
<div class="order-no-cell">
|
||||||
<ElTooltip :content="scope.row.order_no" placement="top" :show-after="300">
|
<ElTooltip :content="scope.row.order_no" placement="top" :show-after="300">
|
||||||
@@ -50,7 +61,16 @@
|
|||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="套餐名称" width="200" show-overflow-tooltip>
|
<ElTableColumn label="套餐名称" width="200" show-overflow-tooltip>
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span class="package-name-text">{{ scope.row.package_name }}</span>
|
<div class="package-name-cell">
|
||||||
|
<span class="package-name-text">{{ scope.row.package_name }}</span>
|
||||||
|
<ElTag
|
||||||
|
v-if="scope.row.relationship_status === 'master_missing'"
|
||||||
|
type="warning"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{{ scope.row.relationship_status_name || '主套餐缺失' }}
|
||||||
|
</ElTag>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn v-if="isAdminOrPlatform" label="套餐成本价格" width="120">
|
<ElTableColumn v-if="isAdminOrPlatform" label="套餐成本价格" width="120">
|
||||||
@@ -659,6 +679,25 @@
|
|||||||
total: props.total
|
total: props.total
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const defaultExpandedPackageUsageIds = computed(() =>
|
||||||
|
props.packageList
|
||||||
|
.filter((pkg) => pkg.expand_by_default && pkg.children?.length)
|
||||||
|
.map((pkg) => pkg.package_usage_id ?? pkg.id)
|
||||||
|
.filter((id): id is number => typeof id === 'number')
|
||||||
|
.map(String)
|
||||||
|
)
|
||||||
|
|
||||||
|
const getLongestOrderNoLength = (packages: PackageInfo[]): number =>
|
||||||
|
packages.reduce((longestLength, pkg) => {
|
||||||
|
const childLongestLength = pkg.children?.length ? getLongestOrderNoLength(pkg.children) : 0
|
||||||
|
return Math.max(longestLength, pkg.order_no?.length || 0, childLongestLength)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
// 为树形展开按钮、层级缩进和单元格留出空间,避免订单号过早省略。
|
||||||
|
const orderNoColumnWidth = computed(() =>
|
||||||
|
Math.min(Math.max(240, getLongestOrderNoLength(props.packageList) * 8 + 80), 500)
|
||||||
|
)
|
||||||
|
|
||||||
// 监听props变化更新分页
|
// 监听props变化更新分页
|
||||||
watch(
|
watch(
|
||||||
() => [props.page, props.pageSize, props.total],
|
() => [props.page, props.pageSize, props.total],
|
||||||
@@ -757,6 +796,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.package-order-column .cell) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.package-name-text {
|
.package-name-text {
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -765,8 +810,20 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.package-name-cell {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
.package-name-text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.order-no-cell {
|
.order-no-cell {
|
||||||
width: 100%;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
:deep(.el-tooltip__trigger) {
|
:deep(.el-tooltip__trigger) {
|
||||||
|
|||||||
@@ -220,11 +220,12 @@ export function useAssetInfo() {
|
|||||||
page_size: pageSize
|
page_size: pageSize
|
||||||
})
|
})
|
||||||
if (response.code === 0 && response.data) {
|
if (response.code === 0 && response.data) {
|
||||||
// 使用分页响应中的 items 数组
|
// items 代表顶层主套餐关系组,必须保留其 children,不得展平。
|
||||||
packageList.value = response.data.items || []
|
const items = response.data.items || []
|
||||||
|
packageList.value = items
|
||||||
// 返回分页信息供外部使用
|
// 返回分页信息供外部使用
|
||||||
return {
|
return {
|
||||||
items: response.data.items || [],
|
items,
|
||||||
total: response.data.total || 0,
|
total: response.data.total || 0,
|
||||||
page: response.data.page || 1,
|
page: response.data.page || 1,
|
||||||
pageSize: response.data.page_size || pageSize
|
pageSize: response.data.page_size || pageSize
|
||||||
|
|||||||
Reference in New Issue
Block a user