fix:通知和设备分配/回收
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m49s

This commit is contained in:
luo
2026-07-28 17:23:23 +08:00
parent b51a3f5537
commit ee266c51cc
7 changed files with 114 additions and 69 deletions

View File

@@ -3,9 +3,7 @@
<div class="notice" @click.stop>
<div class="header">
<span class="text">通知</span>
<button class="read-all" type="button" @click="handleMarkAllRead">
全部已读
</button>
<button class="read-all" type="button" @click="handleMarkAllRead">全部已读</button>
</div>
<div class="bar">
@@ -119,7 +117,7 @@
ElMessage.info(item.body || '该通知暂无可跳转目标')
return
}
if (!navigateNotificationTarget(router, targetResponse.data)) {
if (!navigateNotificationTarget(router, item.ref_type, targetResponse.data)) {
ElMessage.info(item.body || '该通知暂无可跳转目标')
} else {
close()

View File

@@ -739,7 +739,7 @@ export const asyncRoutes: AppRouteRecord[] = [
{
path: 'agent-fund-overview',
name: 'AgentFundOverview',
component: RoutesAlias.AgentFundOverview,
component: RoutesAlias.AgentFundOverviewComponent,
meta: {
title: 'menus.commission.agentCommission',
keepAlive: true,

View File

@@ -89,7 +89,8 @@ export enum RoutesAlias {
// 佣金管理
WithdrawalApproval = '/commission-management/withdrawal-approval', // 提现审批
MyCommission = '/commission-management/my-commission', // 我的佣金
AgentFundOverview = '/commission-management/agent-fund-overview', // 代理商资金概况
AgentFundOverview = '/commission/agent-fund-overview', // 代理商资金概况
AgentFundOverviewComponent = '/commission-management/agent-fund-overview', // 代理商资金概况组件路径
// 设置管理
WithdrawalSettings = '/settings/withdrawal-settings', // 提现配置

View File

@@ -1,6 +1,22 @@
export type NotificationCategory = 'approval' | 'expiry' | 'sync' | 'system' | string
export type NotificationSeverity = 'info' | 'warning' | 'error' | 'critical' | string
/** 后台通知引用的受控资源类型。 */
export type NotificationRefType =
| 'system_config'
| 'integration_log'
| 'package'
| 'asset'
| 'refund'
| 'agent_recharge'
| 'wecom_approval'
| 'iot_card'
| 'device'
| 'expiring_asset'
| 'shop_fund'
| 'card_sync'
| (string & {})
export interface NotificationItem {
id: number
title: string
@@ -11,7 +27,7 @@ export interface NotificationItem {
is_read: boolean
created_at: string
read_at: string | null
ref_type: string | null
ref_type: NotificationRefType | null
ref_id: string | null
ref_key: string | null
}
@@ -63,7 +79,7 @@ export interface NotificationReadAllResponse {
export interface NotificationTarget {
available: boolean
target_id: number | null
target_id: number | string | null
target_key: string
target_type: string
}

View File

@@ -1,70 +1,56 @@
import type { Router } from 'vue-router'
import type { NotificationTarget } from '@/types/api'
import type { RouteLocationRaw, Router } from 'vue-router'
import type { NotificationRefType, NotificationTarget } from '@/types/api'
import { RoutesAlias } from '@/router/routesAlias'
type TargetRoute = {
path: string
query?: Record<string, string>
type TargetRoute = RouteLocationRaw
const resolveTargetRoute = (
refType: NotificationRefType | null,
target: NotificationTarget
): TargetRoute | null => {
const targetId = target.target_id === null ? '' : String(target.target_id)
switch (refType) {
case 'system_config':
return { path: RoutesAlias.SystemConfigs }
case 'refund':
return { path: RoutesAlias.RefundManagement }
case 'agent_recharge':
return { path: RoutesAlias.AgentRecharge }
case 'iot_card':
return targetId ? { path: RoutesAlias.AssetInformation, query: { iccid: targetId } } : null
case 'device':
return targetId
? { path: RoutesAlias.AssetInformation, query: { virtual_no: targetId } }
: null
case 'expiring_asset':
return { path: RoutesAlias.ExpiringAssets }
case 'shop_fund':
return { path: RoutesAlias.AgentFundOverview }
case 'integration_log':
case 'package':
case 'asset':
case 'wecom_approval':
case 'card_sync':
default:
return null
}
}
const resolveTargetRoute = (target: NotificationTarget): TargetRoute | null => {
const targetId = target.target_id === null ? undefined : String(target.target_id)
const targetKey = target.target_key || ''
if (target.target_type === 'refund_detail' && targetId) {
return { path: `${RoutesAlias.RefundDetail}/${targetId}` }
export const navigateNotificationTarget = (
router: Router,
refType: NotificationRefType | null,
target?: NotificationTarget | null
) => {
if (!target) return false
if (!target.available) {
void router.push(RoutesAlias.Exception403)
return true
}
if (target.target_type === 'agent_recharge_detail' && targetId) {
return { path: `${RoutesAlias.AgentRechargeDetail}/${targetId}` }
}
if (target.target_type === 'iot_card_detail' && (targetKey || targetId)) {
return {
path: RoutesAlias.AssetInformation,
query: { iccid: targetKey || targetId || '' }
}
}
if (target.target_type === 'device_detail' && (targetKey || targetId)) {
return {
path: RoutesAlias.AssetInformation,
query: { virtual_no: targetKey || targetId || '' }
}
}
if (
target.target_type === 'expiring_asset_list' ||
target.target_key === 'expiring_assets' ||
target.target_key === 'expiring_asset_list'
) {
return { path: RoutesAlias.ExpiringAssets }
}
if (target.target_type === 'shop_fund_summary') {
return {
path: RoutesAlias.AgentFundOverview,
query: targetId ? { shop_id: targetId } : undefined
}
}
if (target.target_type === 'system_config') {
return {
path: RoutesAlias.SystemConfigs,
query: targetKey ? { config_key: targetKey } : undefined
}
}
return null
}
export const navigateNotificationTarget = (router: Router, target?: NotificationTarget | null) => {
if (!target?.available) return false
const route = resolveTargetRoute(target)
const route = resolveTargetRoute(refType, target)
if (!route) return false
const resolved = router.resolve({ path: route.path, query: route.query })
if (!resolved.matched.length || resolved.name === 'Exception404') return false
void router.push(resolved)
void router.push(route)
return true
}

View File

@@ -831,15 +831,20 @@
try {
const response = await CardService.setSpeedTier(iccid, data.code)
if (response.code !== 0) {
speedLimitDialogVisible.value = false
await nextTick()
ElMessage.error(response.msg || '限速设置失败')
return
}
ElMessage.success(`限速设置成功:${response.data.speed_tier_name}`)
speedLimitDialogVisible.value = false
await nextTick()
ElMessage.success(`限速设置成功:${response.data.speed_tier_name}`)
await handleRefresh()
} catch (error) {
console.error('设置限速失败:', error)
speedLimitDialogVisible.value = false
await nextTick()
ElMessage.error(normalizeApiError(error).message)
} finally {
speedTierSubmitting.value = false

View File

@@ -17,6 +17,21 @@
@refresh="handleRefresh"
>
<template #left>
<ElButton
type="primary"
@click="handleBatchAllocate"
:disabled="!selectedDevices.length"
v-permission="'device:batch_allocate'"
>
批量分配
</ElButton>
<ElButton
@click="handleBatchRecall"
:disabled="!selectedDevices.length"
v-permission="'device:batch_recall'"
>
批量回收
</ElButton>
<ElButton
type="primary"
:disabled="!selectedDevices.length"
@@ -2350,6 +2365,19 @@
})
}
// 批量分配(设备列表原有操作,和设备批量任务页面并行保留)
const handleBatchAllocate = async () => {
if (selectedDevices.value.length === 0) {
ElMessage.warning('请先选择要分配的设备')
return
}
allocateForm.target_shop_id = undefined
allocateForm.remark = ''
allocateResult.value = null
shopCascadeOptions.value = []
allocateDialogVisible.value = true
}
// 确认批量分配
const handleConfirmAllocate = async () => {
if (!allocateFormRef.value) return
@@ -2400,6 +2428,17 @@
}
}
// 批量回收(设备列表原有操作,和设备批量任务页面并行保留)
const handleBatchRecall = () => {
if (selectedDevices.value.length === 0) {
ElMessage.warning('请先选择要回收的设备')
return
}
recallForm.remark = ''
recallResult.value = null
recallDialogVisible.value = true
}
// 确认批量回收
const handleConfirmRecall = async () => {
recallLoading.value = true