This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
:disabled="v.disabled || false"
|
||||
>
|
||||
</el-option>
|
||||
<el-option v-if="options.length === 0" label="无数据" value="__NO_DATA__" disabled />
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
@@ -39,7 +40,7 @@
|
||||
|
||||
// 合并默认配置和自定义配置
|
||||
const config = reactive({
|
||||
placeholder: `${t('table.searchBar.searchSelectPlaceholder')}${prop.item.label}`, // 修改默认placeholder文案
|
||||
placeholder: `${t('table.searchBar.searchSelectPlaceholder')}${prop.item.label}`,
|
||||
...(prop.item.config || {})
|
||||
})
|
||||
|
||||
@@ -56,14 +57,12 @@
|
||||
// 计算属性:处理选项数据
|
||||
const options = computed(() => {
|
||||
if (prop.item.options) {
|
||||
// 判断options是数组还是函数
|
||||
if (Array.isArray(prop.item.options)) {
|
||||
return prop.item.options
|
||||
} else {
|
||||
return prop.item.options()
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
return []
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -209,6 +209,7 @@ export interface AssetPackageUsageRecord {
|
||||
virtual_remain_mb?: number // 剩余虚流量 MB
|
||||
virtual_ratio?: number // 虚流量比例(real/virtual)
|
||||
enable_virtual_data?: boolean // 是否启用虚流量
|
||||
paid_amount?: number // 实际支付金额(分)
|
||||
package_price?: number // 套餐价格(分)
|
||||
activated_at?: string // 激活时间
|
||||
expires_at?: string // 到期时间
|
||||
|
||||
@@ -53,6 +53,7 @@ export interface Order {
|
||||
actual_paid_amount?: number // 实付金额(分)
|
||||
paid_at: string | null
|
||||
commission_status: OrderCommissionStatus
|
||||
commission_status_name?: string // 佣金状态名称(中文)
|
||||
commission_config_version: number
|
||||
device_id: number | null
|
||||
iot_card_id: number | null
|
||||
@@ -71,13 +72,15 @@ export interface OrderQueryParams {
|
||||
page?: number
|
||||
page_size?: number
|
||||
payment_status?: PaymentStatus
|
||||
payment_method?: OrderPaymentMethod
|
||||
order_type?: OrderType
|
||||
order_no?: string
|
||||
identifier?: string // 按资产标识符过滤
|
||||
identifier?: string // 按资产标识符过滤(ICCID 或 VirtualNo)
|
||||
purchase_role?: string // 订单渠道
|
||||
buyer_phone?: string // 按买家手机号精确过滤
|
||||
start_time?: string
|
||||
end_time?: string
|
||||
is_expired?: boolean // 是否已过期
|
||||
dateRange?: string[] | any // For date range picker in UI
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,12 @@ export interface Refund {
|
||||
id: number
|
||||
refund_no: string
|
||||
order_id: number
|
||||
order_no: string // 订单号
|
||||
shop_id: number
|
||||
shop_name: string // 店铺名称
|
||||
package_usage_id: number | null
|
||||
asset_identifier: string // 资产标识符
|
||||
asset_type: string // 资产类型
|
||||
requested_refund_amount: number // 申请退款金额(分)
|
||||
approved_refund_amount: number | null // 实际退款金额(分)
|
||||
actual_received_amount: number // 实收金额(分)
|
||||
@@ -41,6 +45,7 @@ export interface RefundQueryParams {
|
||||
status?: RefundStatus
|
||||
order_id?: number
|
||||
shop_id?: number
|
||||
shop_name?: string // 店铺名称(模糊查询)
|
||||
dateRange?: string[]
|
||||
}
|
||||
|
||||
|
||||
@@ -278,7 +278,7 @@
|
||||
// 响应式表单数据
|
||||
const formFilters = reactive({ ...initialSearchState })
|
||||
|
||||
// 店铺和企业列表
|
||||
// 店铺和企业列表
|
||||
const shopList = ref<any[]>([])
|
||||
const enterpriseList = ref<any[]>([])
|
||||
|
||||
@@ -359,7 +359,18 @@
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: handleShopSearch,
|
||||
placeholder: '请输入店铺名称搜索'
|
||||
placeholder: '请输入店铺名称搜索',
|
||||
reserveKeyword: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
options: STATUS_SELECT_OPTIONS,
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择状态'
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -375,17 +386,8 @@
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: handleEnterpriseSearch,
|
||||
placeholder: '请输入企业名称搜索'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
options: STATUS_SELECT_OPTIONS,
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择状态'
|
||||
placeholder: '请输入企业名称搜索',
|
||||
reserveKeyword: true
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
@@ -194,6 +194,11 @@
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="实名认证策略" width="120">
|
||||
<template #default="scope">
|
||||
{{ getRealnamePolicyName(scope.row.realname_policy) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="实名时间" width="180">
|
||||
<template #default="scope">
|
||||
{{ scope.row.real_name_at ? formatDateTime(scope.row.real_name_at) : '-' }}
|
||||
@@ -229,7 +234,7 @@
|
||||
|
||||
<!-- 设备实时信息 -->
|
||||
<ElDivider content-position="left">设备实时信息</ElDivider>
|
||||
<div v-if="deviceRealtime" style="margin-top: 16px">
|
||||
<div v-if="deviceRealtime" v-loading="realtimeLoading" style="margin-top: 16px">
|
||||
<ElDescriptions :column="4" border>
|
||||
<!-- 基本状态 -->
|
||||
<ElDescriptionsItem label="在线状态">
|
||||
@@ -522,9 +527,12 @@
|
||||
cardInfo: AssetInfo
|
||||
deviceRealtime: DeviceRealtimeInfo | null
|
||||
pollingEnabled: boolean
|
||||
realtimeLoading?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
realtimeLoading: false
|
||||
})
|
||||
|
||||
const { hasAuth } = useAuth()
|
||||
|
||||
|
||||
@@ -2,66 +2,9 @@
|
||||
<ElCard shadow="never" class="info-card current-package-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<div class="card-header-left">
|
||||
<span class="header-title">当前生效套餐</span>
|
||||
<ElTag
|
||||
v-if="currentPackage"
|
||||
:type="getPackageStatusTagType(currentPackage.status)"
|
||||
size="small"
|
||||
>
|
||||
{{ currentPackage?.status_name || '-' }}
|
||||
</ElTag>
|
||||
</div>
|
||||
|
||||
<!-- 中间:流量进度 -->
|
||||
<div v-if="currentPackage && !props.boundDeviceId" class="header-progress">
|
||||
<div class="header-progress-info">
|
||||
<span class="progress-title">流量</span>
|
||||
<span class="progress-text">
|
||||
<template v-if="isAdminOrPlatform">
|
||||
总量 {{ formatDataSize(currentPackage.data_limit_mb || currentPackage.package_total_data || 0) }} / 实际可用
|
||||
{{ formatDataSize(currentPackage.enable_virtual_data ? (currentPackage.virtual_limit_mb || 0) : (currentPackage.data_limit_mb || currentPackage.package_total_data || 0)) }} / 剩余可用
|
||||
{{ formatDataSize(currentPackage.enable_virtual_data ? (currentPackage.virtual_remain_mb || 0) : ((currentPackage.data_limit_mb || currentPackage.package_total_data || 0) - (currentPackage.data_usage_mb || 0))) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
总量 {{ formatDataSize(currentPackage.data_limit_mb || currentPackage.package_total_data || 0) }} / 剩余可用
|
||||
{{ formatDataSize(currentPackage.enable_virtual_data ? (currentPackage.virtual_remain_mb || 0) : ((currentPackage.data_limit_mb || currentPackage.package_total_data || 0) - (currentPackage.data_usage_mb || 0))) }}
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<ElProgress
|
||||
:percentage="
|
||||
currentPackage.enable_virtual_data
|
||||
? getUsageProgress(
|
||||
currentPackage.virtual_data_used || 0,
|
||||
currentPackage.virtual_data_total || 0
|
||||
)
|
||||
: getUsageProgress(
|
||||
currentPackage.real_data_used || 0,
|
||||
currentPackage.real_data_total || 0
|
||||
)
|
||||
"
|
||||
:color="
|
||||
currentPackage.enable_virtual_data
|
||||
? getProgressColorByPercentage(
|
||||
getUsageProgress(
|
||||
currentPackage.virtual_data_used || 0,
|
||||
currentPackage.virtual_data_total || 0
|
||||
)
|
||||
)
|
||||
: getProgressColorByPercentage(
|
||||
getUsageProgress(
|
||||
currentPackage.real_data_used || 0,
|
||||
currentPackage.real_data_total || 0
|
||||
)
|
||||
)
|
||||
"
|
||||
:stroke-width="10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="currentPackage" class="card-header-right">
|
||||
<ElButton type="primary" size="small" @click="handleShowRecharge"> 套餐充值 </ElButton>
|
||||
<span class="header-title">当前生效套餐</span>
|
||||
<div class="header-actions">
|
||||
<slot name="header-actions" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -86,33 +29,80 @@
|
||||
<!-- 显示错误信息 -->
|
||||
<ElAlert v-else-if="errorMsg" :title="errorMsg" type="warning" :closable="false" />
|
||||
|
||||
<!-- 显示套餐详情 -->
|
||||
<ElDescriptions v-else-if="currentPackage" :column="3" border>
|
||||
<ElDescriptionsItem label="套餐名称">
|
||||
{{ currentPackage.package_name || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐价格">
|
||||
{{ formatAmount(currentPackage.package_price) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐类型">
|
||||
{{ getPackageTypeName(currentPackage.package_type) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem
|
||||
v-if="currentPackage.enable_virtual_data && isAdminOrPlatform"
|
||||
label="虚流量比例"
|
||||
>
|
||||
{{ currentPackage.virtual_ratio || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="currentPackage.duration_days" label="套餐时长" :span="3">
|
||||
{{ currentPackage.duration_days }}天
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="开始时间">
|
||||
{{ formatDateTime(currentPackage.start_time) || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="到期时间">
|
||||
{{ formatDateTime(currentPackage.expire_time) || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
<template v-else-if="currentPackage">
|
||||
<!-- 流量使用情况 - 横向进度条卡片 -->
|
||||
<div class="flow-progress-card">
|
||||
<div class="flow-stats-row">
|
||||
<div class="flow-stat-item">
|
||||
<span class="flow-stat-label">总量</span>
|
||||
<span class="flow-stat-value">
|
||||
{{ formatDataSize(currentPackage.data_limit_mb || currentPackage.package_total_data || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="isAdminOrPlatform && currentPackage.enable_virtual_data" class="flow-stat-item">
|
||||
<span class="flow-stat-label">实际可用</span>
|
||||
<span class="flow-stat-value">
|
||||
{{ formatDataSize(currentPackage.virtual_limit_mb || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flow-stat-item">
|
||||
<span class="flow-stat-label">剩余可用</span>
|
||||
<span class="flow-stat-value">
|
||||
{{
|
||||
formatDataSize(
|
||||
currentPackage.enable_virtual_data
|
||||
? currentPackage.virtual_remain_mb || 0
|
||||
: (currentPackage.data_limit_mb || currentPackage.package_total_data || 0) - (currentPackage.data_usage_mb || 0)
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flow-progress-bar">
|
||||
<ElProgress
|
||||
:percentage="
|
||||
currentPackage.enable_virtual_data
|
||||
? getUsageProgress(currentPackage.virtual_data_used || 0, currentPackage.virtual_data_total || 0)
|
||||
: getUsageProgress(currentPackage.real_data_used || 0, currentPackage.real_data_total || 0)
|
||||
"
|
||||
:color="
|
||||
currentPackage.enable_virtual_data
|
||||
? getProgressColorByPercentage(getUsageProgress(currentPackage.virtual_data_used || 0, currentPackage.virtual_data_total || 0))
|
||||
: getProgressColorByPercentage(getUsageProgress(currentPackage.real_data_used || 0, currentPackage.real_data_total || 0))
|
||||
"
|
||||
:stroke-width="14"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 套餐详情表格 -->
|
||||
<ElDescriptions :column="3" border class="package-info-table">
|
||||
<ElDescriptionsItem label="套餐名称">
|
||||
{{ currentPackage.package_name || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐价格">
|
||||
{{ formatAmount(currentPackage.paid_amount || currentPackage.package_price || 0) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="套餐类型">
|
||||
{{ getPackageTypeName(currentPackage.package_type) }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="isAdminOrPlatform && currentPackage.enable_virtual_data" label="虚流量比例">
|
||||
{{ currentPackage.virtual_ratio || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="开始时间">
|
||||
{{ formatDateTime(currentPackage.start_time) || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="到期时间">
|
||||
{{ formatDateTime(currentPackage.expire_time) || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="购买时间">
|
||||
{{ formatDateTime(currentPackage.created_at) || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem v-if="currentPackage.duration_days" label="套餐时长">
|
||||
{{ currentPackage.duration_days }}天
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
</template>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<ElEmpty v-else-if="!loading" :description="errorMsg || '暂无当前生效套餐'" :image-size="100" />
|
||||
@@ -122,7 +112,6 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ElCard,
|
||||
ElTag,
|
||||
ElButton,
|
||||
ElDescriptions,
|
||||
ElDescriptionsItem,
|
||||
@@ -136,21 +125,19 @@
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import type { PackageInfo } from '../types'
|
||||
|
||||
// Use formatters
|
||||
const {
|
||||
formatAmount,
|
||||
formatDataSize,
|
||||
getPackageStatusTagType,
|
||||
getUsageProgress,
|
||||
getProgressColorByPercentage
|
||||
} = useAssetFormatters()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { info: userInfo } = storeToRefs(userStore)
|
||||
const isSuperAdmin = computed(() => userInfo.value.user_type === 1)
|
||||
const isAdminOrPlatform = computed(() => userInfo.value.user_type === 1 || userInfo.value.user_type === 2)
|
||||
const isAdminOrPlatform = computed(
|
||||
() => userInfo.value.user_type === 1 || userInfo.value.user_type === 2
|
||||
)
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
currentPackage: PackageInfo | null
|
||||
loading?: boolean
|
||||
@@ -168,18 +155,11 @@
|
||||
boundDeviceName: ''
|
||||
})
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
showRecharge: []
|
||||
navigateToDevice: [deviceNo: string]
|
||||
}>()
|
||||
|
||||
// Methods
|
||||
const handleShowRecharge = () => {
|
||||
emit('showRecharge')
|
||||
}
|
||||
|
||||
// 套餐类型名称
|
||||
const getPackageTypeName = (type?: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
formal: '正式套餐',
|
||||
@@ -195,52 +175,49 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.card-header-left {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header-right {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.header-progress {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0 24px;
|
||||
.flow-progress-card {
|
||||
padding: 20px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.header-progress-info {
|
||||
.flow-stats-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
margin-bottom: 6px;
|
||||
gap: 32px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.progress-title {
|
||||
flex-shrink: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.flow-stat-item {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
|
||||
.progress-text {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.flow-stat-label {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.flow-stat-value {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flow-progress-bar {
|
||||
padding: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.package-info-table {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>套餐列表</span>
|
||||
<div class="card-header-right">
|
||||
<ElButton type="primary" @click="handleShowRecharge">套餐充值</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="package-table-wrapper">
|
||||
<div v-loading="loading" class="package-table-wrapper">
|
||||
<!-- 绑定设备提示 -->
|
||||
<ElEmpty v-if="props.boundDeviceId" :image-size="100">
|
||||
<template #description>
|
||||
@@ -36,7 +39,7 @@
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="套餐价格" width="120">
|
||||
<template #default="scope">
|
||||
{{ formatAmount(scope.row.package_price || 0) }}
|
||||
{{ formatAmount(scope.row.paid_amount || scope.row.package_price || 0) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="status_name" label="套餐状态" width="100">
|
||||
@@ -104,6 +107,11 @@
|
||||
{{ formatDateTime(scope.row.expires_at) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="created_at" label="创建时间" width="170" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ formatDateTime(scope.row.created_at) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
<ElPagination
|
||||
v-model:current-page="pagination.page"
|
||||
@@ -152,6 +160,7 @@
|
||||
pageSize?: number
|
||||
boundDeviceId?: number
|
||||
boundDeviceNo?: string
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -159,12 +168,14 @@
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
boundDeviceId: undefined,
|
||||
boundDeviceNo: ''
|
||||
boundDeviceNo: '',
|
||||
loading: false
|
||||
})
|
||||
|
||||
// Emits
|
||||
interface Emits {
|
||||
(e: 'showDailyRecords', payload: { packageRow: PackageInfo }): void
|
||||
(e: 'showRecharge'): void
|
||||
(e: 'page-change', page: number): void
|
||||
(e: 'size-change', size: number): void
|
||||
(e: 'navigateToDevice', deviceNo: string): void
|
||||
@@ -212,6 +223,10 @@
|
||||
emit('showDailyRecords', { packageRow })
|
||||
}
|
||||
|
||||
const handleShowRecharge = () => {
|
||||
emit('showRecharge')
|
||||
}
|
||||
|
||||
// 页码变化
|
||||
const handlePageChange = (page: number) => {
|
||||
emit('page-change', page)
|
||||
@@ -229,6 +244,13 @@
|
||||
width: 100%;
|
||||
overflow: hidden; // 防止内容溢出
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.package-table-wrapper {
|
||||
.table-scroll-container {
|
||||
overflow-x: auto;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ElCard shadow="never" class="info-card wallet-info">
|
||||
<ElCard shadow="never" class="info-card wallet-info" v-loading="walletLoading">
|
||||
<template #header>
|
||||
<div class="card-header wallet-header">
|
||||
<div class="header-left">
|
||||
|
||||
@@ -29,6 +29,8 @@ export function useAssetInfo() {
|
||||
const packageList = ref<AssetPackageUsageRecord[]>([])
|
||||
const walletInfo = ref<AssetWalletResponse | null>(null)
|
||||
const walletLoading = ref(false)
|
||||
const packageLoading = ref(false)
|
||||
const realtimeLoading = ref(false)
|
||||
|
||||
/**
|
||||
* 获取资产详情
|
||||
@@ -142,6 +144,7 @@ export function useAssetInfo() {
|
||||
*/
|
||||
const loadPackageList = async (identifier: string, page: number = 1, pageSize: number = 10) => {
|
||||
try {
|
||||
packageLoading.value = true
|
||||
const response = await AssetService.getAssetPackages(identifier, {
|
||||
page,
|
||||
page_size: pageSize
|
||||
@@ -161,6 +164,8 @@ export function useAssetInfo() {
|
||||
} catch (error) {
|
||||
console.error('获取套餐列表失败:', error)
|
||||
return { items: [], total: 0, page: 1, pageSize }
|
||||
} finally {
|
||||
packageLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,23 +178,17 @@ export function useAssetInfo() {
|
||||
currentPackageLoading.value = true
|
||||
currentPackageErrorMsg.value = ''
|
||||
|
||||
const [currentPkgResponse, packageListResponse] = await Promise.all([
|
||||
AssetService.getCurrentPackage(identifier),
|
||||
AssetService.getAssetPackages(identifier, { page: 1, page_size: 50 })
|
||||
])
|
||||
const response = await AssetService.getCurrentPackage(identifier)
|
||||
|
||||
const activePackage = packageListResponse.data?.items?.find((p: any) => p.status === 1)
|
||||
const activePaidAmount = activePackage?.paid_amount || 0
|
||||
|
||||
if (currentPkgResponse.code === 0) {
|
||||
if (currentPkgResponse.data) {
|
||||
const pkg = currentPkgResponse.data
|
||||
if (response.code === 0) {
|
||||
if (response.data) {
|
||||
const pkg = response.data
|
||||
|
||||
currentPackage.value = {
|
||||
id: pkg.package_usage_id || 0,
|
||||
package_id: pkg.package_id || 0,
|
||||
package_name: pkg.package_name || '',
|
||||
package_price: activePaidAmount,
|
||||
paid_amount: pkg.paid_amount || 0,
|
||||
package_total_data: pkg.data_limit_mb || 0,
|
||||
real_data_used: pkg.data_usage_mb || 0,
|
||||
real_data_remaining: (pkg.data_limit_mb || 0) - (pkg.data_usage_mb || 0),
|
||||
@@ -208,7 +207,8 @@ export function useAssetInfo() {
|
||||
data_limit_mb: pkg.data_limit_mb,
|
||||
virtual_limit_mb: pkg.virtual_limit_mb,
|
||||
virtual_remain_mb: pkg.virtual_remain_mb,
|
||||
data_usage_mb: pkg.data_usage_mb
|
||||
data_usage_mb: pkg.data_usage_mb,
|
||||
created_at: pkg.created_at || ''
|
||||
}
|
||||
|
||||
// 同时更新 cardInfo 中的流量显示信息(根据是否启用虚流量选择数据源)
|
||||
@@ -260,6 +260,7 @@ export function useAssetInfo() {
|
||||
*/
|
||||
const loadRealtimeStatus = async (identifier: string, assetType: string) => {
|
||||
try {
|
||||
realtimeLoading.value = true
|
||||
const response = await AssetService.getRealtimeStatus(identifier)
|
||||
if (response.code === 0 && response.data) {
|
||||
const data = response.data
|
||||
@@ -323,6 +324,8 @@ export function useAssetInfo() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取实时状态失败:', error)
|
||||
} finally {
|
||||
realtimeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,8 +380,10 @@ export function useAssetInfo() {
|
||||
currentPackageLoading,
|
||||
currentPackageErrorMsg,
|
||||
packageList,
|
||||
packageLoading,
|
||||
walletInfo,
|
||||
walletLoading,
|
||||
realtimeLoading,
|
||||
|
||||
// Methods
|
||||
fetchAssetDetail,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
:card-info="cardInfo"
|
||||
:device-realtime="deviceRealtime"
|
||||
v-model:polling-enabled="pollingEnabled"
|
||||
:realtime-loading="realtimeLoading"
|
||||
@enable-card="handleEnableCard"
|
||||
@disable-card="handleDisableCard"
|
||||
@manual-deactivate="handleManualDeactivate"
|
||||
@@ -56,7 +57,9 @@
|
||||
:page-size="packagePagination.pageSize"
|
||||
:bound-device-id="cardInfo.bound_device_id"
|
||||
:bound-device-no="cardInfo.bound_device_no"
|
||||
:loading="packageLoading"
|
||||
@show-daily-records="handleShowDailyRecords"
|
||||
@show-recharge="showRechargeDialog"
|
||||
@page-change="handlePackagePageChange"
|
||||
@size-change="handlePackageSizeChange"
|
||||
@navigate-to-device="handleNavigateToDevice"
|
||||
@@ -172,8 +175,10 @@
|
||||
currentPackageLoading,
|
||||
currentPackageErrorMsg,
|
||||
packageList,
|
||||
packageLoading,
|
||||
walletInfo,
|
||||
walletLoading,
|
||||
realtimeLoading,
|
||||
fetchAssetDetail,
|
||||
loadPackageList,
|
||||
loadCurrentPackage,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
label-width="100"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
@@ -340,7 +341,11 @@
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="6">
|
||||
<ElFormItem label="搜索类型" prop="search_type">
|
||||
<ElSelect v-model="bindCardForm.search_type" placeholder="请选择搜索类型" style="width: 100%">
|
||||
<ElSelect
|
||||
v-model="bindCardForm.search_type"
|
||||
placeholder="请选择搜索类型"
|
||||
style="width: 100%"
|
||||
>
|
||||
<ElOption label="ICCID" value="iccid" />
|
||||
<ElOption label="运营商名称" value="carrier_name" />
|
||||
</ElSelect>
|
||||
@@ -655,6 +660,10 @@
|
||||
})
|
||||
const seriesBindingResult = ref<BatchSetDeviceSeriesBindingResponse | null>(null)
|
||||
|
||||
// 搜索表单店铺/系列选项
|
||||
const shopOptions = ref<any[]>([])
|
||||
const searchSeriesOptions = ref<any[]>([])
|
||||
|
||||
// 设备详情弹窗相关
|
||||
const deviceDetailDialogVisible = ref(false)
|
||||
const deviceDetailLoading = ref(false)
|
||||
@@ -734,7 +743,12 @@
|
||||
status: undefined as DeviceStatus | undefined,
|
||||
batch_no: '',
|
||||
device_type: '',
|
||||
manufacturer: ''
|
||||
manufacturer: '',
|
||||
shop_id: undefined as number | undefined,
|
||||
series_id: undefined as number | undefined,
|
||||
dateRange: [] as string[],
|
||||
created_at_start: '',
|
||||
created_at_end: ''
|
||||
}
|
||||
|
||||
// 搜索表单
|
||||
@@ -801,6 +815,54 @@
|
||||
clearable: true,
|
||||
placeholder: '请输入制造商'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '店铺名称',
|
||||
prop: 'shop_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchShops(query),
|
||||
loading: false,
|
||||
placeholder: '请选择或搜索店铺名称'
|
||||
},
|
||||
options: () =>
|
||||
shopOptions.value.map((shop) => ({
|
||||
label: shop.shop_name,
|
||||
value: shop.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '套餐系列',
|
||||
prop: 'series_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchSeries(query),
|
||||
loading: seriesLoading.value,
|
||||
placeholder: '请选择或搜索套餐系列'
|
||||
},
|
||||
options: () =>
|
||||
searchSeriesOptions.value.map((s) => ({
|
||||
label: s.series_name,
|
||||
value: s.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '起始至结束',
|
||||
prop: 'dateRange',
|
||||
type: 'date',
|
||||
config: {
|
||||
type: 'datetimerange',
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
valueFormat: 'YYYY-MM-DDTHH:mm:ssZZ'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -814,21 +876,26 @@
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: '设备号', prop: 'virtual_no' },
|
||||
{ label: 'IMEI', prop: 'imei' },
|
||||
{ label: '设备名称', prop: 'device_name' },
|
||||
{ label: '设备型号', prop: 'device_model' },
|
||||
{ label: '设备类型', prop: 'device_type' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '套餐系列', prop: 'series_name' },
|
||||
{ label: '设备型号', prop: 'device_model' },
|
||||
{ label: '设备类型', prop: 'device_type' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '状态名称', prop: 'status_name' },
|
||||
{ label: '在线状态', prop: 'online_status' },
|
||||
{ label: '实名策略', prop: 'realname_policy' },
|
||||
{ label: '切卡模式', prop: 'switch_mode' },
|
||||
{ label: '激活时间', prop: 'activated_at' },
|
||||
{ label: '制造商', prop: 'manufacturer' },
|
||||
{ label: '软件版本', prop: 'software_version' },
|
||||
{ label: '最大插槽数', prop: 'max_sim_slots' },
|
||||
{ label: '已绑定卡数', prop: 'bound_card_count' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '在线状态', prop: 'online_status' },
|
||||
|
||||
{ label: '切卡模式', prop: 'switch_mode' },
|
||||
{ label: '序列号', prop: 'sn' },
|
||||
{ label: '批次号', prop: 'batch_no' },
|
||||
{ label: '最后在线时间', prop: 'last_online_time' },
|
||||
{ label: '最后同步时间', prop: 'last_gateway_sync_at' },
|
||||
{ label: '批次号', prop: 'batch_no' },
|
||||
{ label: '创建时间', prop: 'created_at' },
|
||||
{ label: '操作', prop: 'operation' }
|
||||
]
|
||||
@@ -875,10 +942,7 @@
|
||||
bindCardForm.iot_card_id = undefined
|
||||
bindCardForm.slot_position = 1
|
||||
// 加载未绑定设备的卡列表
|
||||
await Promise.all([
|
||||
loadDeviceCards(device.virtual_no),
|
||||
loadDefaultIotCards()
|
||||
])
|
||||
await Promise.all([loadDeviceCards(device.virtual_no), loadDefaultIotCards()])
|
||||
}
|
||||
|
||||
// 加载设备绑定的卡列表
|
||||
@@ -1055,7 +1119,7 @@
|
||||
{
|
||||
prop: 'virtual_no',
|
||||
label: '设备号',
|
||||
minWidth: 150,
|
||||
minWidth: 170,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => {
|
||||
return h(
|
||||
@@ -1071,21 +1135,18 @@
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'imei',
|
||||
label: 'IMEI',
|
||||
minWidth: 150,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => row.imei || '-'
|
||||
},
|
||||
{
|
||||
prop: 'device_name',
|
||||
label: '设备名称',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'device_model',
|
||||
label: '设备型号',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'device_type',
|
||||
label: '设备类型',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: 'shop_name',
|
||||
label: '店铺名称',
|
||||
@@ -1097,23 +1158,14 @@
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'manufacturer',
|
||||
label: '制造商',
|
||||
prop: 'device_model',
|
||||
label: '设备型号',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'max_sim_slots',
|
||||
label: '最大插槽数',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: 'bound_card_count',
|
||||
label: '已绑定卡数',
|
||||
width: 110,
|
||||
formatter: (row: Device) => {
|
||||
const color = row.bound_card_count > 0 ? '#67c23a' : '#909399'
|
||||
return h('span', { style: { color, fontWeight: 'bold' } }, row.bound_card_count)
|
||||
}
|
||||
prop: 'device_type',
|
||||
label: '设备类型',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
@@ -1130,6 +1182,12 @@
|
||||
return h(ElTag, { type: status.type }, () => status.text)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'status_name',
|
||||
label: '状态名称',
|
||||
width: 100,
|
||||
formatter: (row: Device) => row.status_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'online_status',
|
||||
label: '在线状态',
|
||||
@@ -1144,6 +1202,20 @@
|
||||
return h(ElTag, { type: status.type }, () => status.text)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'realname_policy',
|
||||
label: '实名策略',
|
||||
width: 150,
|
||||
formatter: (row: Device) => {
|
||||
const policy = row.realname_policy || ''
|
||||
const policyMap: Record<string, string> = {
|
||||
none: '无需实名',
|
||||
before_order: '先实名后充值/购买',
|
||||
after_order: '先充值/购买后实名'
|
||||
}
|
||||
return policyMap[policy] || (policy ? policy : '未知')
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'switch_mode',
|
||||
label: '切卡模式',
|
||||
@@ -1157,19 +1229,51 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'realname_policy',
|
||||
label: '实名认证策略',
|
||||
width: 150,
|
||||
prop: 'activated_at',
|
||||
label: '激活时间',
|
||||
width: 180,
|
||||
formatter: (row: Device) => (row.activated_at ? formatDateTime(row.activated_at) : '-')
|
||||
},
|
||||
{
|
||||
prop: 'manufacturer',
|
||||
label: '制造商',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
prop: 'software_version',
|
||||
label: '软件版本',
|
||||
minWidth: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => row.software_version || '-'
|
||||
},
|
||||
{
|
||||
prop: 'max_sim_slots',
|
||||
label: '最大插槽数',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: 'bound_card_count',
|
||||
label: '已绑定卡数',
|
||||
width: 110,
|
||||
formatter: (row: Device) => {
|
||||
const policy = row.realname_policy || ''
|
||||
const policyMap: Record<string, string> = {
|
||||
none: '无需实名',
|
||||
before_order: '先实名后充值/购买',
|
||||
after_order: '先充值/购买后实名'
|
||||
}
|
||||
return policyMap[policy] || (policy ? policy : '未知')
|
||||
const color = row.bound_card_count > 0 ? '#67c23a' : '#909399'
|
||||
return h('span', { style: { color, fontWeight: 'bold' } }, row.bound_card_count)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'sn',
|
||||
label: '序列号',
|
||||
minWidth: 120,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => row.sn || '-'
|
||||
},
|
||||
{
|
||||
prop: 'batch_no',
|
||||
label: '批次号',
|
||||
minWidth: 180,
|
||||
showOverflowTooltip: true,
|
||||
formatter: (row: Device) => row.batch_no || '-'
|
||||
},
|
||||
{
|
||||
prop: 'last_online_time',
|
||||
label: '最后在线时间',
|
||||
@@ -1184,12 +1288,6 @@
|
||||
formatter: (row: Device) =>
|
||||
row.last_gateway_sync_at ? formatDateTime(row.last_gateway_sync_at) : '-'
|
||||
},
|
||||
{
|
||||
prop: 'batch_no',
|
||||
label: '批次号',
|
||||
minWidth: 180,
|
||||
formatter: (row: Device) => row.batch_no || '-'
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
@@ -1198,9 +1296,50 @@
|
||||
}
|
||||
])
|
||||
|
||||
// 搜索店铺(用于搜索表单)
|
||||
const searchShops = 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 searchSeries = async (query: string) => {
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
status: 1
|
||||
}
|
||||
if (query) {
|
||||
params.series_name = query
|
||||
}
|
||||
const res = await PackageSeriesService.getPackageSeries(params)
|
||||
if (res.code === 0) {
|
||||
searchSeriesOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索套餐系列失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
let isFirstActivation = true
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
searchShops('')
|
||||
searchSeries('')
|
||||
})
|
||||
|
||||
// 当页面被 keep-alive 激活时自动刷新数据
|
||||
@@ -1223,7 +1362,11 @@
|
||||
status: searchForm.status,
|
||||
batch_no: searchForm.batch_no || undefined,
|
||||
device_type: searchForm.device_type || undefined,
|
||||
manufacturer: searchForm.manufacturer || undefined
|
||||
manufacturer: searchForm.manufacturer || undefined,
|
||||
shop_id: searchForm.shop_id || undefined,
|
||||
series_id: searchForm.series_id || undefined,
|
||||
created_at_start: searchForm.created_at_start || undefined,
|
||||
created_at_end: searchForm.created_at_end || undefined
|
||||
}
|
||||
const res = await DeviceService.getDevices(params)
|
||||
if (res.code === 0 && res.data) {
|
||||
@@ -1246,6 +1389,14 @@
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
// 处理日期范围
|
||||
if (searchForm.dateRange && Array.isArray(searchForm.dateRange)) {
|
||||
searchForm.created_at_start = searchForm.dateRange[0] || ''
|
||||
searchForm.created_at_end = searchForm.dateRange[1] || ''
|
||||
} else {
|
||||
searchForm.created_at_start = ''
|
||||
searchForm.created_at_end = ''
|
||||
}
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
const searchForm = reactive({
|
||||
status: undefined,
|
||||
identifier: '',
|
||||
created_at_range: [],
|
||||
created_at_start: '',
|
||||
created_at_end: ''
|
||||
})
|
||||
@@ -306,11 +307,11 @@
|
||||
prop: 'created_at_range',
|
||||
type: 'date',
|
||||
config: {
|
||||
type: 'datetimerange',
|
||||
type: 'daterange',
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
startPlaceholder: '开始日期',
|
||||
endPlaceholder: '结束日期',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -417,12 +418,23 @@
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
// 处理日期范围
|
||||
if (searchForm.created_at_range && Array.isArray(searchForm.created_at_range)) {
|
||||
searchForm.created_at_start = searchForm.created_at_range[0] || ''
|
||||
searchForm.created_at_end = searchForm.created_at_range[1] || ''
|
||||
} else {
|
||||
searchForm.created_at_start = ''
|
||||
searchForm.created_at_end = ''
|
||||
}
|
||||
pagination.page = 1
|
||||
loadExchangeList()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const handleReset = () => {
|
||||
searchForm.created_at_range = []
|
||||
searchForm.created_at_start = ''
|
||||
searchForm.created_at_end = ''
|
||||
pagination.page = 1
|
||||
loadExchangeList()
|
||||
}
|
||||
|
||||
@@ -562,7 +562,13 @@
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import { CardService, ShopService, PackageSeriesService, AssetService, CarrierService } from '@/api/modules'
|
||||
import {
|
||||
CardService,
|
||||
ShopService,
|
||||
PackageSeriesService,
|
||||
AssetService,
|
||||
CarrierService
|
||||
} from '@/api/modules'
|
||||
import { ElMessage, ElTag, ElIcon, ElMessageBox } from 'element-plus'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
import RealnamePolicyDialog from '@/components/device/RealnamePolicyDialog.vue'
|
||||
@@ -659,6 +665,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 套餐系列搜索相关
|
||||
const searchSeriesOptions = ref<any[]>([])
|
||||
|
||||
// 搜索套餐系列(用于搜索栏)
|
||||
const handleSearchSeries = async (query: string) => {
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
status: 1
|
||||
}
|
||||
if (query) {
|
||||
params.series_name = query
|
||||
}
|
||||
const res = await PackageSeriesService.getPackageSeries(params)
|
||||
if (res.code === 0) {
|
||||
searchSeriesOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索套餐系列失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 卡详情弹窗相关
|
||||
const cardDetailDialogVisible = ref(false)
|
||||
const cardDetailLoading = ref(false)
|
||||
@@ -767,6 +796,7 @@
|
||||
msisdn: string
|
||||
virtual_no: string
|
||||
device_virtual_no: string
|
||||
series_id: undefined | number
|
||||
is_distributed: undefined | number
|
||||
is_standalone: undefined | boolean
|
||||
[key: string]: any
|
||||
@@ -777,6 +807,7 @@
|
||||
msisdn: '',
|
||||
virtual_no: '',
|
||||
device_virtual_no: '',
|
||||
series_id: undefined,
|
||||
is_distributed: undefined,
|
||||
is_standalone: undefined,
|
||||
is_replaced: undefined,
|
||||
@@ -952,6 +983,23 @@
|
||||
placeholder: '请输入绑定设备虚拟号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '套餐系列',
|
||||
prop: 'series_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: handleSearchSeries,
|
||||
placeholder: '请选择或搜索套餐系列'
|
||||
},
|
||||
options: () =>
|
||||
searchSeriesOptions.value.map((s) => ({
|
||||
label: s.series_name,
|
||||
value: s.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '是否已分销',
|
||||
prop: 'is_distributed',
|
||||
@@ -974,8 +1022,8 @@
|
||||
placeholder: '全部'
|
||||
},
|
||||
options: () => [
|
||||
{ label: '是(未绑定设备)', value: true },
|
||||
{ label: '否(已绑定设备)', value: false }
|
||||
{ label: '未绑定设备', value: true },
|
||||
{ label: '已绑定设备', value: false }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1016,18 +1064,28 @@
|
||||
{ label: 'ICCID', prop: 'iccid' },
|
||||
{ label: 'MSISDN', prop: 'msisdn' },
|
||||
{ label: '卡虚拟号', prop: 'virtual_no' },
|
||||
{ label: '绑定设备虚拟号', prop: 'device_virtual_no' },
|
||||
{ label: '卡业务类型', prop: 'card_category' },
|
||||
{ label: '运营商', prop: 'carrier_name' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '套餐系列', prop: 'series_name' },
|
||||
{ label: '卡业务类型', prop: 'card_category' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '激活状态', prop: 'activation_status' },
|
||||
{ label: '激活时间', prop: 'activated_at' },
|
||||
{ label: '网络状态', prop: 'network_status' },
|
||||
{ label: '实名状态', prop: 'real_name_status' },
|
||||
{ label: '实名认证策略', prop: 'realname_policy' },
|
||||
{ label: '绑定设备虚拟号', prop: 'device_virtual_no' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '套餐系列', prop: 'series_name' },
|
||||
{ label: '自然月累计流量(MB)', prop: 'current_month_usage_mb' },
|
||||
{ label: '本月开始日期', prop: 'current_month_start_date' },
|
||||
{ label: '上月流量总量(MB)', prop: 'last_month_total_mb' },
|
||||
{ label: '累计流量(MB)', prop: 'data_usage_mb' },
|
||||
{ label: '创建时间', prop: 'created_at' }
|
||||
{ label: '最后流量检查时间', prop: 'last_data_check_at' },
|
||||
{ label: '最后实名检查时间', prop: 'last_real_name_check_at' },
|
||||
{ label: '启用轮询', prop: 'enable_polling' },
|
||||
{ label: '批次号', prop: 'batch_no' },
|
||||
{ label: '供应商', prop: 'supplier' },
|
||||
{ label: '创建时间', prop: 'created_at' },
|
||||
{ label: '更新时间', prop: 'updated_at' }
|
||||
]
|
||||
|
||||
const cardList = ref<StandaloneIotCard[]>([])
|
||||
@@ -1233,12 +1291,70 @@
|
||||
return text
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'activated_at',
|
||||
label: '激活时间',
|
||||
width: 180,
|
||||
formatter: (row: StandaloneIotCard) =>
|
||||
row.activated_at ? formatDateTime(row.activated_at) : '-'
|
||||
},
|
||||
{
|
||||
prop: 'current_month_usage_mb',
|
||||
label: '自然月累计流量(MB)',
|
||||
width: 180,
|
||||
formatter: (row: StandaloneIotCard) => row.current_month_usage_mb?.toLocaleString() || '0'
|
||||
},
|
||||
{
|
||||
prop: 'current_month_start_date',
|
||||
label: '本月开始日期',
|
||||
width: 200,
|
||||
formatter: (row: StandaloneIotCard) =>
|
||||
row.current_month_start_date ? formatDateTime(row.current_month_start_date) : '-'
|
||||
},
|
||||
{
|
||||
prop: 'last_month_total_mb',
|
||||
label: '上月流量总量(MB)',
|
||||
width: 150,
|
||||
formatter: (row: StandaloneIotCard) => row.last_month_total_mb?.toLocaleString() || '0'
|
||||
},
|
||||
{
|
||||
prop: 'data_usage_mb',
|
||||
label: '累计流量(MB)',
|
||||
width: 120,
|
||||
formatter: (row: StandaloneIotCard) => row.data_usage_mb?.toLocaleString() || '0'
|
||||
},
|
||||
{
|
||||
prop: 'last_data_check_at',
|
||||
label: '最后流量检查时间',
|
||||
width: 180,
|
||||
formatter: (row: StandaloneIotCard) =>
|
||||
row.last_data_check_at ? formatDateTime(row.last_data_check_at) : '-'
|
||||
},
|
||||
{
|
||||
prop: 'last_real_name_check_at',
|
||||
label: '最后实名检查时间',
|
||||
width: 180,
|
||||
formatter: (row: StandaloneIotCard) =>
|
||||
row.last_real_name_check_at ? formatDateTime(row.last_real_name_check_at) : '-'
|
||||
},
|
||||
{
|
||||
prop: 'enable_polling',
|
||||
label: '启用轮询',
|
||||
width: 100,
|
||||
formatter: (row: StandaloneIotCard) => (row.enable_polling ? '是' : '否')
|
||||
},
|
||||
{
|
||||
prop: 'batch_no',
|
||||
label: '批次号',
|
||||
width: 200,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'supplier',
|
||||
label: '供应商',
|
||||
width: 150,
|
||||
formatter: (row: StandaloneIotCard) => row.supplier || '-'
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
@@ -1289,7 +1405,6 @@
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSearchCarrierOptions()
|
||||
getTableData()
|
||||
})
|
||||
|
||||
|
||||
@@ -59,7 +59,27 @@
|
||||
},
|
||||
{ label: '资产标识符', prop: 'asset_identifier' },
|
||||
{ label: '来源所有者', prop: 'from_owner_name' },
|
||||
{
|
||||
label: '来源类型',
|
||||
formatter: (_, data) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
platform: '平台',
|
||||
shop: '店铺'
|
||||
}
|
||||
return typeMap[data.from_owner_type] || data.from_owner_type || '-'
|
||||
}
|
||||
},
|
||||
{ label: '目标所有者', prop: 'to_owner_name' },
|
||||
{
|
||||
label: '目标类型',
|
||||
formatter: (_, data) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
platform: '平台',
|
||||
shop: '店铺'
|
||||
}
|
||||
return typeMap[data.to_owner_type] || data.to_owner_type || '-'
|
||||
}
|
||||
},
|
||||
{ label: '操作人', prop: 'operator_name' },
|
||||
{ label: '关联卡数量', prop: 'related_card_count' },
|
||||
{ label: '创建时间', prop: 'created_at', formatter: (value) => formatDateTime(value) },
|
||||
|
||||
@@ -28,28 +28,13 @@
|
||||
:pageSize="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
:row-class-name="getRowClassName"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
@row-contextmenu="handleRowContextMenu"
|
||||
@cell-mouse-enter="handleCellMouseEnter"
|
||||
@cell-mouse-leave="handleCellMouseLeave"
|
||||
>
|
||||
<template #default>
|
||||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 鼠标悬浮提示 -->
|
||||
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<ArtMenuRight
|
||||
ref="contextMenuRef"
|
||||
:menu-items="contextMenuItems"
|
||||
:menu-width="120"
|
||||
@select="handleContextMenuSelect"
|
||||
/>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
@@ -58,7 +43,7 @@
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { CardService } from '@/api/modules'
|
||||
import { CardService, ShopService } from '@/api/modules'
|
||||
import { ElMessage, ElTag } from 'element-plus'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
@@ -67,28 +52,14 @@
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import type { AssetAllocationRecord, AllocationTypeEnum, AssetTypeEnum } from '@/types/api/card'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||
|
||||
defineOptions({ name: 'AssetAllocationRecords' })
|
||||
|
||||
const { hasAuth } = useAuth()
|
||||
const router = useRouter()
|
||||
|
||||
// 使用表格右键菜单功能
|
||||
const {
|
||||
showContextMenuHint,
|
||||
hintPosition,
|
||||
getRowClassName,
|
||||
handleCellMouseEnter,
|
||||
handleCellMouseLeave
|
||||
} = useTableContextMenu()
|
||||
|
||||
const loading = ref(false)
|
||||
const tableRef = ref()
|
||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
const currentRow = ref<AssetAllocationRecord | null>(null)
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
@@ -99,6 +70,7 @@
|
||||
from_shop_id: undefined as number | undefined,
|
||||
to_shop_id: undefined as number | undefined,
|
||||
operator_id: undefined as number | undefined,
|
||||
dateRange: [] as string[],
|
||||
created_at_start: '',
|
||||
created_at_end: ''
|
||||
}
|
||||
@@ -159,16 +131,51 @@
|
||||
placeholder: 'ICCID或设备号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '来源店铺',
|
||||
prop: 'from_shop_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchShops(query, 'from'),
|
||||
placeholder: '请选择或搜索来源店铺'
|
||||
},
|
||||
options: () =>
|
||||
fromShopOptions.value.map((shop) => ({
|
||||
label: shop.shop_name,
|
||||
value: shop.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '目标店铺',
|
||||
prop: 'to_shop_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchShops(query, 'to'),
|
||||
placeholder: '请选择或搜索目标店铺'
|
||||
},
|
||||
options: () =>
|
||||
toShopOptions.value.map((shop) => ({
|
||||
label: shop.shop_name,
|
||||
value: shop.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'created_at_range',
|
||||
prop: 'dateRange',
|
||||
type: 'date',
|
||||
config: {
|
||||
type: 'datetimerange',
|
||||
type: 'daterange',
|
||||
clearable: true,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
valueFormat: 'YYYY-MM-DDTHH:mm:ssZ'
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始日期',
|
||||
endPlaceholder: '结束日期',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -186,6 +193,33 @@
|
||||
{ label: '创建时间', prop: 'created_at' }
|
||||
]
|
||||
|
||||
// 店铺搜索选项
|
||||
const fromShopOptions = ref<any[]>([])
|
||||
const toShopOptions = ref<any[]>([])
|
||||
|
||||
// 搜索店铺
|
||||
const searchShops = async (query: string, type: 'from' | 'to') => {
|
||||
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) {
|
||||
if (type === 'from') {
|
||||
fromShopOptions.value = res.data.items || []
|
||||
} else {
|
||||
toShopOptions.value = res.data.items || []
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索店铺失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const recordList = ref<AssetAllocationRecord[]>([])
|
||||
|
||||
// 获取分配类型标签类型
|
||||
@@ -246,12 +280,38 @@
|
||||
{
|
||||
prop: 'from_owner_name',
|
||||
label: '来源所有者',
|
||||
width: 150
|
||||
width: 150,
|
||||
formatter: (row: AssetAllocationRecord) => row.from_owner_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'from_owner_type',
|
||||
label: '来源类型',
|
||||
width: 100,
|
||||
formatter: (row: AssetAllocationRecord) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
platform: '平台',
|
||||
shop: '店铺'
|
||||
}
|
||||
return typeMap[row.from_owner_type] || row.from_owner_type || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'to_owner_name',
|
||||
label: '目标所有者',
|
||||
width: 150
|
||||
width: 150,
|
||||
formatter: (row: AssetAllocationRecord) => row.to_owner_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'to_owner_type',
|
||||
label: '目标类型',
|
||||
width: 100,
|
||||
formatter: (row: AssetAllocationRecord) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
platform: '平台',
|
||||
shop: '店铺'
|
||||
}
|
||||
return typeMap[row.to_owner_type] || row.to_owner_type || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'operator_name',
|
||||
@@ -279,6 +339,8 @@
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
searchShops('', 'from')
|
||||
searchShops('', 'to')
|
||||
})
|
||||
|
||||
// 获取分配记录列表
|
||||
@@ -291,13 +353,6 @@
|
||||
...formFilters
|
||||
}
|
||||
|
||||
// 处理日期范围
|
||||
if ((params as any).created_at_range && (params as any).created_at_range.length === 2) {
|
||||
params.created_at_start = (params as any).created_at_range[0]
|
||||
params.created_at_end = (params as any).created_at_range[1]
|
||||
delete (params as any).created_at_range
|
||||
}
|
||||
|
||||
// 清理空值
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (params[key] === '' || params[key] === undefined) {
|
||||
@@ -327,6 +382,14 @@
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
// 处理日期范围
|
||||
if (formFilters.dateRange && Array.isArray(formFilters.dateRange)) {
|
||||
formFilters.created_at_start = formFilters.dateRange[0] || ''
|
||||
formFilters.created_at_end = formFilters.dateRange[1] || ''
|
||||
} else {
|
||||
formFilters.created_at_start = ''
|
||||
formFilters.created_at_end = ''
|
||||
}
|
||||
pagination.page = 1
|
||||
getTableData()
|
||||
}
|
||||
@@ -362,40 +425,10 @@
|
||||
ElMessage.warning('您没有查看详情的权限')
|
||||
}
|
||||
}
|
||||
|
||||
// 右键菜单项配置
|
||||
const contextMenuItems = computed((): MenuItemType[] => {
|
||||
const items: MenuItemType[] = []
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
// 处理表格行右键菜单
|
||||
const handleRowContextMenu = (row: AssetAllocationRecord, column: any, event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
currentRow.value = row
|
||||
contextMenuRef.value?.show(event)
|
||||
}
|
||||
|
||||
// 处理右键菜单选择
|
||||
const handleContextMenuSelect = (item: MenuItemType) => {
|
||||
if (!currentRow.value) return
|
||||
|
||||
switch (
|
||||
item.key
|
||||
// No cases available
|
||||
) {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.asset-allocation-records-page {
|
||||
// Allocation records page styles
|
||||
}
|
||||
|
||||
:deep(.el-table__row.table-row-with-context-menu) {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { AuthorizationService } from '@/api/modules'
|
||||
import { AuthorizationService, EnterpriseService } from '@/api/modules'
|
||||
import { ElMessage, ElTag } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
@@ -125,6 +125,28 @@
|
||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
const currentRow = ref<AuthorizationItem | null>(null)
|
||||
|
||||
// 企业搜索选项
|
||||
const enterpriseOptions = ref<any[]>([])
|
||||
|
||||
// 搜索企业
|
||||
const searchEnterprises = async (query: string) => {
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 20
|
||||
}
|
||||
if (query) {
|
||||
params.enterprise_name = query
|
||||
}
|
||||
const res = await EnterpriseService.getEnterprises(params)
|
||||
if (res.code === 0) {
|
||||
enterpriseOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索企业失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索表单初始值
|
||||
const initialSearchState = {
|
||||
enterprise_id: undefined as number | undefined,
|
||||
@@ -165,6 +187,23 @@
|
||||
placeholder: '请输入ICCID'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '企业名称',
|
||||
prop: 'enterprise_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: searchEnterprises,
|
||||
placeholder: '请选择或搜索企业'
|
||||
},
|
||||
options: () =>
|
||||
enterpriseOptions.value.map((e) => ({
|
||||
label: e.enterprise_name,
|
||||
value: e.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '授权人类型',
|
||||
prop: 'authorizer_type',
|
||||
@@ -309,6 +348,7 @@
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
searchEnterprises('')
|
||||
})
|
||||
|
||||
// 获取授权记录列表
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
const loading = ref(false)
|
||||
const detailData = ref<AgentRecharge | null>(null)
|
||||
|
||||
const rechargeId = computed(() => Number(route.params.id))
|
||||
const pageTitle = computed(() => `充值订单详情 #${rechargeId.value}`)
|
||||
const pageTitle = computed(() => `充值订单详情`)
|
||||
|
||||
// 格式化货币 - 将分转换为元
|
||||
const formatCurrency = (amount: number): string => {
|
||||
@@ -159,7 +158,7 @@
|
||||
const loadDetailData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await AgentRechargeService.getAgentRechargeById(rechargeId.value)
|
||||
const res = await AgentRechargeService.getAgentRechargeById(Number(route.params.id))
|
||||
if (res.code === 0) {
|
||||
detailData.value = res.data
|
||||
} else {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
:show-expand="false"
|
||||
label-width="100"
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
@@ -214,6 +215,7 @@
|
||||
const initialSearchState: AgentRechargeQueryParams = {
|
||||
shop_id: undefined,
|
||||
status: undefined,
|
||||
dateRange: [],
|
||||
start_date: '',
|
||||
end_date: ''
|
||||
}
|
||||
@@ -288,11 +290,12 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
label: '开始至结束',
|
||||
prop: 'dateRange',
|
||||
type: 'daterange',
|
||||
type: 'date',
|
||||
config: {
|
||||
clearable: true,
|
||||
type: 'daterange',
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始日期',
|
||||
endPlaceholder: '结束日期',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
@@ -414,7 +417,7 @@
|
||||
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
|
||||
onClick: (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
handleViewDetail(row)
|
||||
handleNameClick(row)
|
||||
}
|
||||
},
|
||||
row.recharge_no
|
||||
@@ -733,6 +736,15 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 处理名称点击
|
||||
const handleNameClick = (row: AgentRecharge) => {
|
||||
if (hasAuth('agent_recharge:detail_page')) {
|
||||
handleViewDetail(row)
|
||||
} else {
|
||||
ElMessage.warning('您没有查看详情的权限')
|
||||
}
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = (row: AgentRecharge) => {
|
||||
router.push({
|
||||
|
||||
@@ -1,77 +1,24 @@
|
||||
<template>
|
||||
<div class="refund-detail-page">
|
||||
<ElCard shadow="never" v-loading="loading">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>退款详情</span>
|
||||
<ElButton @click="handleGoBack">返回</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
<ElCard shadow="never">
|
||||
<!-- 页面头部 -->
|
||||
<div class="detail-header">
|
||||
<ElButton @click="handleBack">
|
||||
<template #icon>
|
||||
<ElIcon><ArrowLeft /></ElIcon>
|
||||
</template>
|
||||
返回
|
||||
</ElButton>
|
||||
<h2 class="detail-title">{{ pageTitle }}</h2>
|
||||
</div>
|
||||
|
||||
<ElDescriptions :column="2" border v-if="refund">
|
||||
<ElDescriptionsItem label="退款单号">{{ refund.refund_no }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="状态">
|
||||
<ElTag :type="getStatusType(refund.status)">{{ getStatusText(refund.status) }}</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<!-- 详情内容 -->
|
||||
<DetailPage v-if="refund" :sections="detailSections" :data="refund" />
|
||||
|
||||
<ElDescriptionsItem label="申请退款金额">
|
||||
{{ formatCurrency(refund.requested_refund_amount) }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="实际退款金额">
|
||||
{{ refund.approved_refund_amount ? formatCurrency(refund.approved_refund_amount) : '-' }}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="实收金额">
|
||||
{{ formatCurrency(refund.actual_received_amount) }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="资产重置">
|
||||
<ElTag :type="refund.asset_reset ? 'success' : 'info'">
|
||||
{{ refund.asset_reset ? '是' : '否' }}
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="佣金扣除">
|
||||
<ElTag :type="refund.commission_deducted ? 'success' : 'info'">
|
||||
{{ refund.commission_deducted ? '是' : '否' }}
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="退款原因">
|
||||
{{ refund.refund_reason || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="拒绝原因">
|
||||
{{ refund.reject_reason || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="审批时间">
|
||||
{{ refund.processed_at ? formatDateTime(refund.processed_at) : '-' }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="审批备注">
|
||||
{{ refund.remark || '-' }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="创建时间">
|
||||
{{ formatDateTime(refund.created_at) }}
|
||||
</ElDescriptionsItem>
|
||||
|
||||
<ElDescriptionsItem label="更新时间">
|
||||
{{ formatDateTime(refund.updated_at) }}
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
|
||||
<div class="action-buttons" v-if="refund">
|
||||
<!-- 待审批状态显示审批操作 -->
|
||||
<template v-if="refund.status === 1">
|
||||
<ElButton type="primary" @click="handleShowApprove">审批通过</ElButton>
|
||||
<ElButton type="danger" @click="handleShowReject">审批拒绝</ElButton>
|
||||
<ElButton type="warning" @click="handleShowReturn">退回</ElButton>
|
||||
</template>
|
||||
<!-- 已拒绝或已退回状态显示重新提交 -->
|
||||
<template v-if="refund.status === 3 || refund.status === 4">
|
||||
<ElButton type="primary" @click="handleShowResubmit">重新提交</ElButton>
|
||||
</template>
|
||||
<!-- 加载中 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<ElIcon class="is-loading"><Loading /></ElIcon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
@@ -247,11 +194,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ref, onMounted, computed, h } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElCard, ElButton, ElIcon, ElMessage, ElTag } from 'element-plus'
|
||||
import { ArrowLeft, Loading } from '@element-plus/icons-vue'
|
||||
import DetailPage from '@/components/common/DetailPage.vue'
|
||||
import type { DetailSection } from '@/components/common/DetailPage.vue'
|
||||
import { RefundService } from '@/api/modules'
|
||||
import { ElMessage, ElTag } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type {
|
||||
Refund,
|
||||
RefundStatus,
|
||||
@@ -261,38 +210,39 @@
|
||||
ResubmitRefundRequest
|
||||
} from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
|
||||
defineOptions({ name: 'RefundDetail' })
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
const approveLoading = ref(false)
|
||||
const rejectLoading = ref(false)
|
||||
const returnLoading = ref(false)
|
||||
const resubmitLoading = ref(false)
|
||||
const refund = ref<Refund | null>(null)
|
||||
|
||||
const pageTitle = computed(() => `退款详情`)
|
||||
|
||||
const approveDialogVisible = ref(false)
|
||||
const rejectDialogVisible = ref(false)
|
||||
const returnDialogVisible = ref(false)
|
||||
const resubmitDialogVisible = ref(false)
|
||||
|
||||
const approveFormRef = ref<FormInstance>()
|
||||
const rejectFormRef = ref<FormInstance>()
|
||||
const returnFormRef = ref<FormInstance>()
|
||||
const resubmitFormRef = ref<FormInstance>()
|
||||
const approveLoading = ref(false)
|
||||
const rejectLoading = ref(false)
|
||||
const returnLoading = ref(false)
|
||||
const resubmitLoading = ref(false)
|
||||
|
||||
const approveRules = reactive<FormRules>({})
|
||||
const approveFormRef = ref()
|
||||
const rejectFormRef = ref()
|
||||
const returnFormRef = ref()
|
||||
const resubmitFormRef = ref()
|
||||
|
||||
const rejectRules = reactive<FormRules>({
|
||||
const approveRules = ref()
|
||||
const rejectRules = ref({
|
||||
reject_reason: [{ required: true, message: '请输入拒绝原因', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const returnRules = reactive<FormRules>({})
|
||||
|
||||
const resubmitRules = reactive<FormRules>({})
|
||||
const returnRules = ref()
|
||||
const resubmitRules = ref()
|
||||
|
||||
const approveForm = reactive<ApproveRefundRequest>({
|
||||
approved_refund_amount: undefined,
|
||||
@@ -317,23 +267,20 @@
|
||||
refund_reason: ''
|
||||
})
|
||||
|
||||
// 格式化货币 - 将分转换为元
|
||||
const formatCurrency = (amount: number): string => {
|
||||
return `¥${(amount / 100).toFixed(2)}`
|
||||
}
|
||||
|
||||
// 获取状态标签类型
|
||||
const getStatusType = (status: RefundStatus): 'warning' | 'success' | 'danger' | 'info' => {
|
||||
const statusMap: Record<RefundStatus, 'warning' | 'success' | 'danger' | 'info'> = {
|
||||
1: 'warning', // 待审批
|
||||
2: 'success', // 已通过
|
||||
3: 'danger', // 已拒绝
|
||||
4: 'info' // 已退回
|
||||
1: 'warning',
|
||||
2: 'success',
|
||||
3: 'danger',
|
||||
4: 'info'
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: RefundStatus): string => {
|
||||
const statusMap: Record<RefundStatus, string> = {
|
||||
1: '待审批',
|
||||
@@ -344,14 +291,94 @@
|
||||
return statusMap[status] || '-'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const id = route.params.id as string
|
||||
if (id) {
|
||||
fetchRefundDetail(Number(id))
|
||||
const detailSections = computed((): DetailSection[] => [
|
||||
{
|
||||
title: '退款信息',
|
||||
fields: [
|
||||
{ label: '退款单号', prop: 'refund_no' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '订单号', prop: 'order_no' },
|
||||
{ label: '资产标识符', prop: 'asset_identifier' },
|
||||
{ label: '资产类型', prop: 'asset_type' },
|
||||
{
|
||||
label: '申请退款金额',
|
||||
formatter: (_, data) => formatCurrency(data.requested_refund_amount)
|
||||
},
|
||||
{
|
||||
label: '实际退款金额',
|
||||
formatter: (_, data) =>
|
||||
data.approved_refund_amount ? formatCurrency(data.approved_refund_amount) : '-'
|
||||
},
|
||||
{
|
||||
label: '实收金额',
|
||||
formatter: (_, data) => formatCurrency(data.actual_received_amount)
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
render: (data) =>
|
||||
h(ElTag, { type: getStatusType(data.status) }, () => getStatusText(data.status))
|
||||
},
|
||||
{
|
||||
label: '资产重置',
|
||||
render: (data) =>
|
||||
h(ElTag, { type: data.asset_reset ? 'success' : 'info' }, () =>
|
||||
data.asset_reset ? '是' : '否'
|
||||
)
|
||||
},
|
||||
{
|
||||
label: '佣金扣除',
|
||||
render: (data) =>
|
||||
h(ElTag, { type: data.commission_deducted ? 'success' : 'info' }, () =>
|
||||
data.commission_deducted ? '是' : '否'
|
||||
)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '退款原因',
|
||||
fields: [
|
||||
{
|
||||
label: '退款原因',
|
||||
prop: 'refund_reason',
|
||||
formatter: (value) => value || '-',
|
||||
fullWidth: true
|
||||
},
|
||||
{
|
||||
label: '拒绝原因',
|
||||
prop: 'reject_reason',
|
||||
formatter: (value) => value || '-',
|
||||
fullWidth: true
|
||||
},
|
||||
{
|
||||
label: '审批备注',
|
||||
prop: 'remark',
|
||||
formatter: (value) => value || '-',
|
||||
fullWidth: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '时间信息',
|
||||
fields: [
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'created_at',
|
||||
formatter: (value) => formatDateTime(value)
|
||||
},
|
||||
{
|
||||
label: '审批时间',
|
||||
prop: 'processed_at',
|
||||
formatter: (value) => (value ? formatDateTime(value) : '-')
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
prop: 'updated_at',
|
||||
formatter: (value) => formatDateTime(value)
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
])
|
||||
|
||||
// 获取退款详情
|
||||
const fetchRefundDetail = async (id: number) => {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -366,24 +393,40 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 返回
|
||||
const handleGoBack = () => {
|
||||
router.back()
|
||||
const handleBack = () => {
|
||||
router.push(RoutesAlias.RefundManagement)
|
||||
}
|
||||
|
||||
// 显示审批通过对话框
|
||||
const handleShowApprove = () => {
|
||||
approveDialogVisible.value = true
|
||||
}
|
||||
onMounted(() => {
|
||||
const id = route.params.id as string
|
||||
if (id) {
|
||||
fetchRefundDetail(Number(id))
|
||||
}
|
||||
})
|
||||
|
||||
// 审批通过对话框关闭后的清理
|
||||
const handleApproveDialogClosed = () => {
|
||||
approveFormRef.value?.resetFields()
|
||||
approveForm.approved_refund_amount = undefined
|
||||
approveForm.remark = ''
|
||||
}
|
||||
|
||||
// 审批通过
|
||||
const handleRejectDialogClosed = () => {
|
||||
rejectFormRef.value?.resetFields()
|
||||
rejectForm.reject_reason = ''
|
||||
}
|
||||
|
||||
const handleReturnDialogClosed = () => {
|
||||
returnFormRef.value?.resetFields()
|
||||
returnForm.remark = ''
|
||||
}
|
||||
|
||||
const handleResubmitDialogClosed = () => {
|
||||
resubmitFormRef.value?.resetFields()
|
||||
resubmitForm.requested_refund_amount = undefined
|
||||
resubmitForm.actual_received_amount = undefined
|
||||
resubmitForm.refund_reason = ''
|
||||
}
|
||||
|
||||
const handleApproveRefund = async () => {
|
||||
if (!approveFormRef.value || !refund.value) return
|
||||
|
||||
@@ -411,18 +454,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 显示审批拒绝对话框
|
||||
const handleShowReject = () => {
|
||||
rejectDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 审批拒绝对话框关闭后的清理
|
||||
const handleRejectDialogClosed = () => {
|
||||
rejectFormRef.value?.resetFields()
|
||||
rejectForm.reject_reason = ''
|
||||
}
|
||||
|
||||
// 审批拒绝
|
||||
const handleRejectRefund = async () => {
|
||||
if (!rejectFormRef.value || !refund.value) return
|
||||
|
||||
@@ -443,18 +474,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 显示退回对话框
|
||||
const handleShowReturn = () => {
|
||||
returnDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 退回对话框关闭后的清理
|
||||
const handleReturnDialogClosed = () => {
|
||||
returnFormRef.value?.resetFields()
|
||||
returnForm.remark = ''
|
||||
}
|
||||
|
||||
// 退回
|
||||
const handleReturnRefund = async () => {
|
||||
if (!returnFormRef.value || !refund.value) return
|
||||
|
||||
@@ -475,25 +494,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 显示重新提交对话框
|
||||
const handleShowResubmit = () => {
|
||||
if (!refund.value) return
|
||||
// 预填充原有数据
|
||||
resubmitForm.requested_refund_amount = refund.value.requested_refund_amount / 100
|
||||
resubmitForm.actual_received_amount = refund.value.actual_received_amount / 100
|
||||
resubmitForm.refund_reason = refund.value.refund_reason
|
||||
resubmitDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 重新提交对话框关闭后的清理
|
||||
const handleResubmitDialogClosed = () => {
|
||||
resubmitFormRef.value?.resetFields()
|
||||
resubmitForm.requested_refund_amount = undefined
|
||||
resubmitForm.actual_received_amount = undefined
|
||||
resubmitForm.refund_reason = ''
|
||||
}
|
||||
|
||||
// 重新提交
|
||||
const handleResubmitRefund = async () => {
|
||||
if (!resubmitFormRef.value || !refund.value) return
|
||||
|
||||
@@ -529,17 +529,32 @@
|
||||
.refund-detail-page {
|
||||
padding: 20px;
|
||||
|
||||
.card-header {
|
||||
.detail-header {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.detail-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
.loading-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 0;
|
||||
color: var(--el-text-color-secondary);
|
||||
|
||||
.el-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -62,7 +62,6 @@
|
||||
:loading="orderSearchLoading"
|
||||
placeholder="请输入订单号搜索"
|
||||
style="width: 100%"
|
||||
@focus="handleOrderSelectFocus"
|
||||
@change="handleOrderSelectChange"
|
||||
>
|
||||
<ElOption
|
||||
@@ -84,11 +83,7 @@
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="实收金额">
|
||||
<ElInput
|
||||
:model-value="selectedOrderActualPaid"
|
||||
disabled
|
||||
style="width: 100%"
|
||||
/>
|
||||
<ElInput :model-value="selectedOrderActualPaid" disabled style="width: 100%" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="退款原因">
|
||||
<ElInput
|
||||
@@ -315,7 +310,6 @@
|
||||
defineOptions({ name: 'RefundList' })
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const { hasAuth } = useAuth()
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -364,7 +358,26 @@
|
||||
})),
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchShops(query)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '订单号',
|
||||
prop: 'order_id',
|
||||
type: 'select',
|
||||
placeholder: '请选择订单号',
|
||||
options: () =>
|
||||
orderOptions.value.map((order) => ({
|
||||
label: `${order.order_no}`,
|
||||
value: order.id
|
||||
})),
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchOrdersForFilter(query)
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -381,21 +394,6 @@
|
||||
config: {
|
||||
clearable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '订单',
|
||||
prop: 'order_id',
|
||||
type: 'select',
|
||||
placeholder: '请选择订单',
|
||||
options: () =>
|
||||
orderOptions.value.map((order) => ({
|
||||
label: `订单号: ${order.order_no} (ID: ${order.id})`,
|
||||
value: order.id
|
||||
})),
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -409,8 +407,10 @@
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: '退款单号', prop: 'refund_no' },
|
||||
{ label: '订单ID', prop: 'order_id' },
|
||||
{ label: '店铺ID', prop: 'shop_id' },
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '订单号', prop: 'order_no' },
|
||||
{ label: '资产标识符', prop: 'asset_identifier' },
|
||||
{ label: '资产类型', prop: 'asset_type' },
|
||||
{ label: '申请退款金额', prop: 'requested_refund_amount' },
|
||||
{ label: '实际退款金额', prop: 'approved_refund_amount' },
|
||||
{ label: '实收金额', prop: 'actual_received_amount' },
|
||||
@@ -523,7 +523,7 @@
|
||||
{
|
||||
prop: 'refund_no',
|
||||
label: '退款单号',
|
||||
minWidth: 240,
|
||||
minWidth: 210,
|
||||
formatter: (row: Refund) => {
|
||||
return h(
|
||||
'span',
|
||||
@@ -531,7 +531,7 @@
|
||||
style: 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;',
|
||||
onClick: (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
handleViewDetail(row)
|
||||
handleNameClick(row)
|
||||
}
|
||||
},
|
||||
row.refund_no
|
||||
@@ -539,14 +539,30 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'order_id',
|
||||
label: '订单ID',
|
||||
width: 120
|
||||
prop: 'shop_name',
|
||||
label: '店铺名称',
|
||||
width: 160
|
||||
},
|
||||
{
|
||||
prop: 'shop_id',
|
||||
label: '店铺ID',
|
||||
width: 120
|
||||
prop: 'order_no',
|
||||
label: '订单号',
|
||||
width: 220
|
||||
},
|
||||
{
|
||||
prop: 'asset_identifier',
|
||||
label: '资产标识符',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
prop: 'asset_type',
|
||||
label: '资产类型',
|
||||
width: 100,
|
||||
formatter: (row: Refund) =>
|
||||
row.asset_type === 'device'
|
||||
? '设备'
|
||||
: row.asset_type === 'iot_card'
|
||||
? 'IoT卡'
|
||||
: row.asset_type || '-'
|
||||
},
|
||||
{
|
||||
prop: 'requested_refund_amount',
|
||||
@@ -629,39 +645,45 @@
|
||||
|
||||
onMounted(() => {
|
||||
getTableData()
|
||||
loadShops()
|
||||
loadOrders()
|
||||
searchShops('')
|
||||
searchOrdersForFilter('')
|
||||
})
|
||||
|
||||
// 加载店铺列表
|
||||
const loadShops = async () => {
|
||||
// 搜索店铺(用于搜索表单)
|
||||
const searchShops = async (query: string) => {
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 9999
|
||||
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('Load shops failed:', error)
|
||||
console.error('Search shops failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载订单列表
|
||||
const loadOrders = async () => {
|
||||
// 搜索订单(用于搜索表单)
|
||||
const searchOrdersForFilter = async (query: string) => {
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
page_size: 9999
|
||||
page_size: 20
|
||||
}
|
||||
if (query) {
|
||||
params.order_no = query
|
||||
}
|
||||
const res = await OrderService.getOrders(params)
|
||||
if (res.code === 0) {
|
||||
orderOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Load orders failed:', error)
|
||||
console.error('Search orders failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -759,14 +781,6 @@
|
||||
createDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 订单选择框获取焦点时加载默认数据
|
||||
const handleOrderSelectFocus = () => {
|
||||
// 如果还没有数据,则加载默认的20条
|
||||
if (orderSearchOptions.value.length === 0) {
|
||||
searchOrders('')
|
||||
}
|
||||
}
|
||||
|
||||
// 对话框关闭后的清理
|
||||
const handleCreateDialogClosed = () => {
|
||||
createFormRef.value?.resetFields()
|
||||
@@ -965,6 +979,15 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 处理名称点击
|
||||
const handleNameClick = (row: Refund) => {
|
||||
if (hasAuth('refund:detail')) {
|
||||
handleViewDetail(row)
|
||||
} else {
|
||||
ElMessage.warning('您没有查看详情的权限')
|
||||
}
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = (row: Refund) => {
|
||||
router.push({
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
:show-expand="false"
|
||||
show-expand
|
||||
@reset="handleReset"
|
||||
label-width="100"
|
||||
@search="handleSearch"
|
||||
@@ -356,8 +356,11 @@
|
||||
order_no: '',
|
||||
buyer_phone: '',
|
||||
payment_status: undefined,
|
||||
payment_method: undefined,
|
||||
order_type: undefined,
|
||||
purchase_role: undefined,
|
||||
identifier: '',
|
||||
is_expired: undefined,
|
||||
start_time: '',
|
||||
end_time: ''
|
||||
}
|
||||
@@ -378,6 +381,28 @@
|
||||
|
||||
// 搜索表单配置
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '订单号',
|
||||
prop: 'order_no',
|
||||
type: 'input',
|
||||
placeholder: '请输入订单号',
|
||||
config: {
|
||||
maxlength: 50,
|
||||
clearable: true
|
||||
},
|
||||
labelWidth: '100'
|
||||
},
|
||||
{
|
||||
label: '资产标识符',
|
||||
prop: 'identifier',
|
||||
type: 'input',
|
||||
placeholder: 'ICCID 或 VirtualNo',
|
||||
config: {
|
||||
maxlength: 30,
|
||||
clearable: true
|
||||
},
|
||||
labelWidth: '100'
|
||||
},
|
||||
{
|
||||
label: '买家手机号',
|
||||
prop: 'buyer_phone',
|
||||
@@ -389,6 +414,21 @@
|
||||
},
|
||||
labelWidth: '100'
|
||||
},
|
||||
{
|
||||
label: '支付方式',
|
||||
prop: 'payment_method',
|
||||
type: 'select',
|
||||
placeholder: '请选择支付方式',
|
||||
options: [
|
||||
{ label: '钱包支付', value: 'wallet' },
|
||||
{ label: '微信支付', value: 'wechat' },
|
||||
{ label: '支付宝支付', value: 'alipay' },
|
||||
{ label: '线下支付', value: 'offline' }
|
||||
],
|
||||
config: {
|
||||
clearable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '支付状态',
|
||||
prop: 'payment_status',
|
||||
@@ -404,6 +444,19 @@
|
||||
clearable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '是否过期',
|
||||
prop: 'is_expired',
|
||||
type: 'select',
|
||||
placeholder: '请选择',
|
||||
options: [
|
||||
{ label: '已过期', value: true },
|
||||
{ label: '未过期', value: false }
|
||||
],
|
||||
config: {
|
||||
clearable: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '订单类型',
|
||||
prop: 'order_type',
|
||||
@@ -433,14 +486,15 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
label: '开始至结束',
|
||||
prop: 'dateRange',
|
||||
type: 'daterange',
|
||||
type: 'date',
|
||||
config: {
|
||||
clearable: true,
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
type: 'daterange',
|
||||
rangeSeparator: '至',
|
||||
startPlaceholder: '开始日期',
|
||||
endPlaceholder: '结束日期',
|
||||
valueFormat: 'YYYY-MM-DD'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -459,15 +513,16 @@
|
||||
{ label: '买家昵称', prop: 'buyer_nickname' },
|
||||
{ label: '订单类型', prop: 'order_type' },
|
||||
{ label: '买家类型', prop: 'buyer_type' },
|
||||
{ label: '资产标识符', prop: 'asset_identifier' },
|
||||
{ label: '订单渠道', prop: 'purchase_role' },
|
||||
{ label: '购买备注', prop: 'purchase_remark' },
|
||||
{ label: '操作者', prop: 'operator_name' },
|
||||
{ label: '支付状态', prop: 'payment_status' },
|
||||
{ label: '佣金状态', prop: 'commission_status_name' },
|
||||
{ label: '订单金额', prop: 'total_amount' },
|
||||
{ label: '实付金额', prop: 'actual_paid_amount' },
|
||||
{ label: '支付方式', prop: 'payment_method' },
|
||||
{ label: '支付时间', prop: 'paid_at' },
|
||||
{ label: '创建时间', prop: 'created_at' }
|
||||
{ label: '支付时间', prop: 'paid_at' }
|
||||
]
|
||||
|
||||
const createFormRef = ref<FormInstance>()
|
||||
@@ -794,6 +849,12 @@
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'asset_identifier',
|
||||
label: '资产标识符',
|
||||
width: 180,
|
||||
formatter: (row: Order) => row.asset_identifier || '-'
|
||||
},
|
||||
{
|
||||
prop: 'purchase_role',
|
||||
label: '订单渠道',
|
||||
@@ -838,6 +899,12 @@
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'commission_status_name',
|
||||
label: '佣金状态',
|
||||
width: 120,
|
||||
formatter: (row: Order) => row.commission_status_name || '-'
|
||||
},
|
||||
{
|
||||
prop: 'total_amount',
|
||||
label: '订单金额',
|
||||
@@ -864,12 +931,6 @@
|
||||
label: '支付时间',
|
||||
width: 180,
|
||||
formatter: (row: Order) => (row.paid_at ? formatDateTime(row.paid_at) : '-')
|
||||
},
|
||||
{
|
||||
prop: 'created_at',
|
||||
label: '创建时间',
|
||||
width: 180,
|
||||
formatter: (row: Order) => formatDateTime(row.created_at)
|
||||
}
|
||||
])
|
||||
|
||||
@@ -896,8 +957,11 @@
|
||||
order_no: searchForm.order_no || undefined,
|
||||
buyer_phone: searchForm.buyer_phone || undefined,
|
||||
payment_status: searchForm.payment_status,
|
||||
payment_method: searchForm.payment_method,
|
||||
order_type: searchForm.order_type,
|
||||
purchase_role: searchForm.purchase_role,
|
||||
identifier: searchForm.identifier || undefined,
|
||||
is_expired: searchForm.is_expired,
|
||||
start_time: searchForm.start_time || undefined,
|
||||
end_time: searchForm.end_time || undefined
|
||||
}
|
||||
|
||||
@@ -511,15 +511,46 @@
|
||||
{ label: '套餐名称', prop: 'package_name' },
|
||||
{ label: '所属系列', prop: 'series_name' },
|
||||
{ label: '套餐类型', prop: 'package_type' },
|
||||
{ label: '套餐周期类型', prop: 'calendar_type' },
|
||||
{ label: '有效期', prop: 'duration_months' },
|
||||
{ label: '真流量', prop: 'real_data_mb' },
|
||||
{ label: '虚流量', prop: 'virtual_data_mb' },
|
||||
{ label: '虚流量比例', prop: 'virtual_ratio' },
|
||||
{ label: '有效期', prop: 'duration_months' },
|
||||
{
|
||||
label: '启用虚流量',
|
||||
prop: 'enable_virtual_data',
|
||||
formatter: (row: any) => (row.enable_virtual_data ? '是' : '否')
|
||||
},
|
||||
{
|
||||
label: '流量重置周期',
|
||||
prop: 'data_reset_cycle',
|
||||
formatter: (row: any) => {
|
||||
const map: Record<string, string> = {
|
||||
daily: '每日',
|
||||
weekly: '每周',
|
||||
monthly: '每月',
|
||||
none: '不重置'
|
||||
}
|
||||
return map[row.data_reset_cycle] || row.data_reset_cycle || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '到期时间基准',
|
||||
prop: 'expiry_base',
|
||||
formatter: (row: any) => {
|
||||
const map: Record<string, string> = {
|
||||
from_activation: '实名激活',
|
||||
from_purchase: '购买时'
|
||||
}
|
||||
return map[row.expiry_base] || row.expiry_base || '-'
|
||||
}
|
||||
},
|
||||
{ label: '成本价', prop: 'cost_price' },
|
||||
{ label: '建议售价', prop: 'suggested_retail_price' },
|
||||
{ label: '上架状态', prop: 'shelf_status' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '创建时间', prop: 'created_at' }
|
||||
{ label: '创建时间', prop: 'created_at' },
|
||||
{ label: '更新时间', prop: 'updated_at' }
|
||||
]
|
||||
|
||||
// 表单验证规则
|
||||
@@ -729,6 +760,44 @@
|
||||
width: 100,
|
||||
formatter: (row: PackageResponse) => `${row.duration_months}月`
|
||||
},
|
||||
{
|
||||
prop: 'calendar_type',
|
||||
label: '套餐周期类型',
|
||||
width: 120,
|
||||
formatter: (row: PackageResponse) => {
|
||||
const map: Record<string, string> = {
|
||||
natural_month: '自然月',
|
||||
by_day: '按天'
|
||||
}
|
||||
return map[row.calendar_type] || row.calendar_type || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'data_reset_cycle',
|
||||
label: '流量重置周期',
|
||||
width: 120,
|
||||
formatter: (row: PackageResponse) => {
|
||||
const map: Record<string, string> = {
|
||||
daily: '每日',
|
||||
weekly: '每周',
|
||||
monthly: '每月',
|
||||
none: '不重置'
|
||||
}
|
||||
return map[row.data_reset_cycle] || row.data_reset_cycle || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'expiry_base',
|
||||
label: '到期时间基准',
|
||||
width: 120,
|
||||
formatter: (row: PackageResponse) => {
|
||||
const map: Record<string, string> = {
|
||||
from_activation: '实名激活',
|
||||
from_purchase: '购买时'
|
||||
}
|
||||
return map[row.expiry_base] || row.expiry_base || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'cost_price',
|
||||
label: '成本价',
|
||||
@@ -850,8 +919,6 @@
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
loadSeriesOptions()
|
||||
loadSearchSeriesOptions()
|
||||
getTableData()
|
||||
})
|
||||
|
||||
|
||||
@@ -460,14 +460,46 @@
|
||||
{ label: '系列编码', prop: 'series_code' },
|
||||
{ label: '系列名称', prop: 'series_name' },
|
||||
{ label: '描述', prop: 'description' },
|
||||
{ label: '一次性佣金', prop: 'enable_one_time_commission' },
|
||||
{ label: '佣金类型', prop: 'commission_type' },
|
||||
{ label: '触发阈值', prop: 'commission_threshold' },
|
||||
{ label: '触发类型', prop: 'trigger_type' },
|
||||
{ label: '强充状态', prop: 'enable_force_recharge' },
|
||||
{ label: '强充金额', prop: 'force_amount' },
|
||||
{ label: '强充计算类型', prop: 'force_calc_type' },
|
||||
{ label: '时效类型', prop: 'validity_type' },
|
||||
{
|
||||
label: '一次性佣金',
|
||||
prop: 'enable_one_time_commission',
|
||||
formatter: (row: any) => (row.enable_one_time_commission ? '启用' : '未启用')
|
||||
},
|
||||
{
|
||||
label: '佣金类型',
|
||||
prop: 'commission_type',
|
||||
formatter: (row: any) => row.one_time_commission_config?.commission_type || '-'
|
||||
},
|
||||
{
|
||||
label: '触发阈值',
|
||||
prop: 'threshold',
|
||||
formatter: (row: any) => row.one_time_commission_config?.threshold ?? '-'
|
||||
},
|
||||
{
|
||||
label: '触发类型',
|
||||
prop: 'trigger_type',
|
||||
formatter: (row: any) => row.one_time_commission_config?.trigger_type || '-'
|
||||
},
|
||||
{
|
||||
label: '强充状态',
|
||||
prop: 'enable_force_recharge',
|
||||
formatter: (row: any) => (row.one_time_commission_config?.enable_force_recharge ? '启用' : '未启用')
|
||||
},
|
||||
{
|
||||
label: '强充金额',
|
||||
prop: 'force_amount',
|
||||
formatter: (row: any) => row.one_time_commission_config?.force_amount ?? '-'
|
||||
},
|
||||
{
|
||||
label: '强充计算类型',
|
||||
prop: 'force_calc_type',
|
||||
formatter: (row: any) => row.one_time_commission_config?.force_calc_type || '-'
|
||||
},
|
||||
{
|
||||
label: '时效类型',
|
||||
prop: 'validity_type',
|
||||
formatter: (row: any) => row.one_time_commission_config?.validity_type || '-'
|
||||
},
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '创建时间', prop: 'created_at' }
|
||||
]
|
||||
|
||||
@@ -459,10 +459,16 @@
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="启用强制充值">
|
||||
<ElSwitch v-model="form.enable_force_recharge" />
|
||||
<ElSwitch
|
||||
v-model="form.enable_force_recharge"
|
||||
:disabled="form.force_recharge_locked && isAgentAccount"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12" v-if="form.enable_force_recharge">
|
||||
<ElCol
|
||||
:span="12"
|
||||
v-if="form.enable_force_recharge && !(form.force_recharge_locked && isAgentAccount)"
|
||||
>
|
||||
<ElFormItem label="强充金额(元)" prop="force_recharge_amount">
|
||||
<ElInputNumber
|
||||
v-model="form.force_recharge_amount"
|
||||
@@ -625,10 +631,13 @@
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="启用强制充值">
|
||||
<ElSwitch v-model="form.enable_force_recharge" />
|
||||
<ElSwitch
|
||||
v-model="form.enable_force_recharge"
|
||||
:disabled="form.force_recharge_locked && isAgentAccount"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12" v-if="form.enable_force_recharge">
|
||||
<ElCol :span="12" v-if="form.enable_force_recharge && !(form.force_recharge_locked && isAgentAccount)">
|
||||
<ElFormItem label="强充金额(元)" prop="force_recharge_amount">
|
||||
<ElInputNumber
|
||||
v-model="form.force_recharge_amount"
|
||||
@@ -783,6 +792,8 @@
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import {
|
||||
CommonStatus,
|
||||
@@ -797,6 +808,9 @@
|
||||
|
||||
const { hasAuth } = useAuth()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const { info: userInfo } = storeToRefs(userStore)
|
||||
const isAgentAccount = computed(() => userInfo.value.user_type === 3)
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
@@ -1036,6 +1050,7 @@
|
||||
}>, // 梯度配置(包含完整字段,从系列继承)
|
||||
enable_force_recharge: false,
|
||||
force_recharge_amount: undefined,
|
||||
force_recharge_locked: false, // 强充是否被套餐系列锁定
|
||||
series_force_recharge_amount: 0, // 系列强充金额(元)
|
||||
packages: [] as Array<{ package_id: number; cost_price: number }> // 套餐配置
|
||||
})
|
||||
@@ -1207,6 +1222,27 @@
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'force_recharge_locked',
|
||||
label: '强充锁定',
|
||||
width: 100,
|
||||
formatter: (row: ShopSeriesGrantResponse) => {
|
||||
return h(
|
||||
ElTag,
|
||||
{ type: row.force_recharge_locked ? 'danger' : 'success', size: 'small' },
|
||||
() => (row.force_recharge_locked ? '是' : '否')
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'force_recharge_amount',
|
||||
label: '强充金额',
|
||||
width: 100,
|
||||
formatter: (row: ShopSeriesGrantResponse) => {
|
||||
if (!row.force_recharge_enabled) return '-'
|
||||
return `¥${((row.force_recharge_amount || 0) / 100).toFixed(2)}`
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'package_count',
|
||||
label: '套餐数量',
|
||||
@@ -1794,6 +1830,7 @@
|
||||
form.force_recharge_amount = detail.force_recharge_amount
|
||||
? detail.force_recharge_amount / 100
|
||||
: undefined
|
||||
form.force_recharge_locked = detail.force_recharge_locked || false
|
||||
|
||||
// 设置套餐数据
|
||||
if (detail.packages && detail.packages.length > 0) {
|
||||
@@ -1842,6 +1879,7 @@
|
||||
form.commission_tiers = []
|
||||
form.enable_force_recharge = false
|
||||
form.force_recharge_amount = undefined
|
||||
form.force_recharge_locked = false
|
||||
form.packages = []
|
||||
selectedPackageIds.value = []
|
||||
packageOptions.value = [] // 清空套餐选项
|
||||
@@ -1874,6 +1912,7 @@
|
||||
form.commission_tiers = []
|
||||
form.enable_force_recharge = false
|
||||
form.force_recharge_amount = undefined
|
||||
form.force_recharge_locked = false
|
||||
form.packages = []
|
||||
selectedPackageIds.value = []
|
||||
}
|
||||
|
||||
@@ -14,16 +14,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
border
|
||||
stripe
|
||||
:row-class-name="getRowClassName"
|
||||
@row-contextmenu="handleRowContextMenu"
|
||||
@cell-mouse-enter="handleCellMouseEnter"
|
||||
@cell-mouse-leave="handleCellMouseLeave"
|
||||
>
|
||||
<el-table v-loading="loading" :data="tableData" border stripe>
|
||||
<el-table-column prop="task_type" label="任务类型" min-width="200" />
|
||||
<el-table-column prop="task_type_name" label="任务名称" min-width="150" />
|
||||
<el-table-column prop="max_concurrency" label="最大并发数" min-width="120" align="center" />
|
||||
@@ -50,18 +41,13 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="handleEdit(row)">修改配置</el-button>
|
||||
<el-button type="danger" link @click="handleReset(row)">重置</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 鼠标悬浮提示 -->
|
||||
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<ArtMenuRight
|
||||
ref="contextMenuRef"
|
||||
:menu-items="contextMenuItems"
|
||||
:menu-width="120"
|
||||
@select="handleContextMenuSelect"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 编辑对话框 -->
|
||||
@@ -94,22 +80,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { PollingConcurrencyService } from '@/api/modules'
|
||||
import type { PollingConcurrency, UpdateConcurrencyParams } from '@/types/api'
|
||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref<PollingConcurrency[]>([])
|
||||
const dialogVisible = ref(false)
|
||||
const formRef = ref<FormInstance>()
|
||||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||||
const currentClickRow = ref<PollingConcurrency | null>(null)
|
||||
|
||||
const currentRow = ref<PollingConcurrency>({
|
||||
task_type: '',
|
||||
@@ -131,27 +111,6 @@
|
||||
]
|
||||
}
|
||||
|
||||
const {
|
||||
showContextMenuHint,
|
||||
hintPosition,
|
||||
getRowClassName,
|
||||
handleCellMouseEnter,
|
||||
handleCellMouseLeave
|
||||
} = useTableContextMenu()
|
||||
|
||||
const contextMenuItems = computed<MenuItemType[]>(() => [
|
||||
{
|
||||
label: '修改配置',
|
||||
key: 'edit',
|
||||
permission: 'polling_concurrency_update'
|
||||
},
|
||||
{
|
||||
label: '重置',
|
||||
key: 'reset',
|
||||
permission: 'polling_concurrency_reset'
|
||||
}
|
||||
])
|
||||
|
||||
const getUtilizationColor = (percentage: number) => {
|
||||
if (percentage >= 90) return '#f56c6c'
|
||||
if (percentage >= 70) return '#e6a23c'
|
||||
@@ -240,28 +199,6 @@
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// 处理表格行右键菜单
|
||||
const handleRowContextMenu = (row: PollingConcurrency, column: any, event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
currentClickRow.value = row
|
||||
contextMenuRef.value?.show(event)
|
||||
}
|
||||
|
||||
// 处理右键菜单选择
|
||||
const handleContextMenuSelect = (item: MenuItemType) => {
|
||||
if (!currentClickRow.value) return
|
||||
|
||||
switch (item.key) {
|
||||
case 'edit':
|
||||
handleEdit(currentClickRow.value)
|
||||
break
|
||||
case 'reset':
|
||||
handleReset(currentClickRow.value)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
@@ -35,28 +35,14 @@
|
||||
:pageSize="pagination.page_size"
|
||||
:total="pagination.total"
|
||||
:marginTop="10"
|
||||
:row-class-name="getRowClassName"
|
||||
:actions="getActions"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
@row-contextmenu="handleRowContextMenu"
|
||||
@cell-mouse-enter="handleCellMouseEnter"
|
||||
@cell-mouse-leave="handleCellMouseLeave"
|
||||
>
|
||||
<template #default>
|
||||
<el-table-column v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 鼠标悬浮提示 -->
|
||||
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<ArtMenuRight
|
||||
ref="contextMenuRef"
|
||||
:menu-items="contextMenuItems"
|
||||
:menu-width="120"
|
||||
@select="handleContextMenuSelect"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
@@ -256,9 +242,7 @@
|
||||
} from '@/types/api'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref<PollingConfig[]>([])
|
||||
@@ -266,8 +250,6 @@
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref<'create' | 'edit'>('create')
|
||||
const formRef = ref<FormInstance>()
|
||||
const contextMenuRef = ref()
|
||||
const currentClickRow = ref<PollingConfig | null>(null)
|
||||
const carrierList = ref<any[]>([])
|
||||
|
||||
// 分页数据
|
||||
@@ -327,14 +309,6 @@
|
||||
]
|
||||
}
|
||||
|
||||
const {
|
||||
showContextMenuHint,
|
||||
hintPosition,
|
||||
getRowClassName,
|
||||
handleCellMouseEnter,
|
||||
handleCellMouseLeave
|
||||
} = useTableContextMenu()
|
||||
|
||||
// 搜索表单配置
|
||||
const searchFormItems = computed<SearchFormItem[]>(() => [
|
||||
{
|
||||
@@ -501,23 +475,28 @@
|
||||
}))
|
||||
)
|
||||
|
||||
const contextMenuItems = computed<MenuItemType[]>(() => [
|
||||
{
|
||||
label: '编辑',
|
||||
key: 'edit',
|
||||
permission: 'polling_config_update'
|
||||
},
|
||||
{
|
||||
label: currentClickRow.value?.status === 1 ? '禁用' : '启用',
|
||||
key: 'toggle',
|
||||
permission: 'polling_config_update_status'
|
||||
},
|
||||
{
|
||||
const getActions = (row: PollingConfig) => {
|
||||
const actions: any[] = [
|
||||
{
|
||||
label: '编辑',
|
||||
handler: () => handleEdit(row)
|
||||
}
|
||||
]
|
||||
|
||||
actions.push({
|
||||
label: row.status === 1 ? '禁用' : '启用',
|
||||
type: row.status === 1 ? 'danger' : 'primary',
|
||||
handler: () => handleToggleStatus(row)
|
||||
})
|
||||
|
||||
actions.push({
|
||||
label: '删除',
|
||||
key: 'delete',
|
||||
permission: 'polling_config_delete'
|
||||
}
|
||||
])
|
||||
type: 'danger',
|
||||
handler: () => handleDelete(row)
|
||||
})
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
const loadCarriers = async (carrierName?: string) => {
|
||||
try {
|
||||
@@ -708,29 +687,6 @@
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
const handleRowContextMenu = (row: PollingConfig, column: any, event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
currentClickRow.value = row
|
||||
contextMenuRef.value?.show(event)
|
||||
}
|
||||
|
||||
const handleContextMenuSelect = (item: MenuItemType) => {
|
||||
if (!currentClickRow.value) return
|
||||
|
||||
switch (item.key) {
|
||||
case 'edit':
|
||||
handleEdit(currentClickRow.value)
|
||||
break
|
||||
case 'toggle':
|
||||
handleToggleStatus(currentClickRow.value)
|
||||
break
|
||||
case 'delete':
|
||||
handleDelete(currentClickRow.value)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadCarriers()
|
||||
loadData()
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
:show-expand="false"
|
||||
show-expand
|
||||
@reset="handleReset"
|
||||
@search="handleSearch"
|
||||
></ArtSearchBar>
|
||||
@@ -347,6 +347,28 @@
|
||||
const availableRoles = ref<ShopRoleResponse[]>([])
|
||||
const selectedRoleId = ref<number | undefined>(undefined)
|
||||
|
||||
// 上级店铺搜索选项
|
||||
const parentShopOptions = ref<any[]>([])
|
||||
|
||||
// 搜索上级店铺
|
||||
const searchParentShops = 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) {
|
||||
parentShopOptions.value = res.data.items || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索店铺失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 定义表单搜索初始值
|
||||
const initialSearchState = {
|
||||
shop_name: '',
|
||||
@@ -408,6 +430,23 @@
|
||||
placeholder: '请输入店铺编号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '上级店铺',
|
||||
prop: 'parent_id',
|
||||
type: 'select',
|
||||
config: {
|
||||
clearable: true,
|
||||
filterable: true,
|
||||
remote: true,
|
||||
remoteMethod: (query: string) => searchParentShops(query),
|
||||
placeholder: '请选择或搜索上级店铺'
|
||||
},
|
||||
options: () =>
|
||||
parentShopOptions.value.map((shop) => ({
|
||||
label: shop.shop_name,
|
||||
value: shop.id
|
||||
}))
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
@@ -722,6 +761,7 @@
|
||||
getShopList()
|
||||
loadTopLevelShops() // 加载顶级店铺用于级联选择
|
||||
searchDefaultRoles('') // 加载初始默认角色列表
|
||||
searchParentShops('') // 加载上级店铺选项
|
||||
})
|
||||
|
||||
// 获取店铺列表
|
||||
|
||||
Reference in New Issue
Block a user