倒计时没有覆盖复机停机的问题
This commit is contained in:
@@ -165,9 +165,8 @@ export class AssetService extends BaseService {
|
|||||||
* @param identifier 资产标识符(ICCID 或 VirtualNo)
|
* @param identifier 资产标识符(ICCID 或 VirtualNo)
|
||||||
*/
|
*/
|
||||||
static stopAsset(identifier: string): Promise<BaseResponse<DeviceStopResponse | void>> {
|
static stopAsset(identifier: string): Promise<BaseResponse<DeviceStopResponse | void>> {
|
||||||
return this.post<BaseResponse<DeviceStopResponse | void>>(
|
return runRateLimitedAssetAction('stop', identifier, () =>
|
||||||
`/api/admin/assets/${identifier}/stop`,
|
this.post<BaseResponse<DeviceStopResponse | void>>(`/api/admin/assets/${identifier}/stop`, {})
|
||||||
{}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const FIVE_MINUTES_MS = 5 * 60 * 1000
|
const FIVE_MINUTES_MS = 5 * 60 * 1000
|
||||||
const STORAGE_KEY_PREFIX = 'asset-action-rate-limit'
|
const STORAGE_KEY_PREFIX = 'asset-action-rate-limit'
|
||||||
|
|
||||||
export type AssetRateLimitedAction = 'refresh' | 'start'
|
export type AssetRateLimitedAction = 'refresh' | 'start' | 'stop'
|
||||||
|
|
||||||
export class FrontendRateLimitError extends Error {
|
export class FrontendRateLimitError extends Error {
|
||||||
readonly remainingMs: number
|
readonly remainingMs: number
|
||||||
|
|||||||
@@ -239,20 +239,22 @@
|
|||||||
type="success"
|
type="success"
|
||||||
size="small"
|
size="small"
|
||||||
link
|
link
|
||||||
|
:disabled="isBindingStartDisabled(scope.row)"
|
||||||
@click="handleEnableBindingCard(scope.row)"
|
@click="handleEnableBindingCard(scope.row)"
|
||||||
v-permission="'iot_info:start'"
|
v-permission="'iot_info:start'"
|
||||||
>
|
>
|
||||||
启用此卡
|
{{ getBindingStartButtonText(scope.row) }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="scope.row.network_status === 1"
|
v-if="scope.row.network_status === 1"
|
||||||
type="warning"
|
type="warning"
|
||||||
size="small"
|
size="small"
|
||||||
link
|
link
|
||||||
|
:disabled="isBindingStopDisabled(scope.row)"
|
||||||
@click="handleDisableBindingCard(scope.row)"
|
@click="handleDisableBindingCard(scope.row)"
|
||||||
v-permission="'iot_info:stop'"
|
v-permission="'iot_info:stop'"
|
||||||
>
|
>
|
||||||
停用此卡
|
{{ getBindingStopButtonText(scope.row) }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -397,17 +399,19 @@
|
|||||||
v-if="cardInfo?.network_status === 0"
|
v-if="cardInfo?.network_status === 0"
|
||||||
v-permission="'iot_info:start'"
|
v-permission="'iot_info:start'"
|
||||||
type="success"
|
type="success"
|
||||||
|
:disabled="startDisabled"
|
||||||
@click="handleEnableCard"
|
@click="handleEnableCard"
|
||||||
>
|
>
|
||||||
启用此卡
|
{{ startRemainingText ? `启用此卡(${startRemainingText})` : '启用此卡' }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="cardInfo?.network_status === 1"
|
v-if="cardInfo?.network_status === 1"
|
||||||
v-permission="'iot_info:stop'"
|
v-permission="'iot_info:stop'"
|
||||||
type="warning"
|
type="warning"
|
||||||
|
:disabled="stopDisabled"
|
||||||
@click="handleDisableCard"
|
@click="handleDisableCard"
|
||||||
>
|
>
|
||||||
停用此卡
|
{{ stopRemainingText ? `停用此卡(${stopRemainingText})` : '停用此卡' }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<!--<ElButton type="danger" v-permission="'iot_info:handler_stop'" @click="handleManualDeactivate"> 手动停用 </ElButton>-->
|
<!--<ElButton type="danger" v-permission="'iot_info:handler_stop'" @click="handleManualDeactivate"> 手动停用 </ElButton>-->
|
||||||
<!-- 手动停用暂时不开放 -->
|
<!-- 手动停用暂时不开放 -->
|
||||||
@@ -580,10 +584,22 @@
|
|||||||
deviceRealtime: DeviceRealtimeInfo | null
|
deviceRealtime: DeviceRealtimeInfo | null
|
||||||
pollingEnabled: boolean
|
pollingEnabled: boolean
|
||||||
realtimeLoading?: boolean
|
realtimeLoading?: boolean
|
||||||
|
startDisabled?: boolean
|
||||||
|
stopDisabled?: boolean
|
||||||
|
startRemainingText?: string
|
||||||
|
stopRemainingText?: string
|
||||||
|
getStartRemainingText?: (identifier: string) => string
|
||||||
|
getStopRemainingText?: (identifier: string) => string
|
||||||
|
isStartDisabled?: (identifier: string) => boolean
|
||||||
|
isStopDisabled?: (identifier: string) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
realtimeLoading: false
|
realtimeLoading: false,
|
||||||
|
startDisabled: false,
|
||||||
|
stopDisabled: false,
|
||||||
|
startRemainingText: '',
|
||||||
|
stopRemainingText: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const { hasAuth } = useAuth()
|
const { hasAuth } = useAuth()
|
||||||
@@ -717,17 +733,15 @@
|
|||||||
|
|
||||||
// 卡操作
|
// 卡操作
|
||||||
const handleEnableCard = () => {
|
const handleEnableCard = () => {
|
||||||
|
if (props.startDisabled) return
|
||||||
emit('enableCard')
|
emit('enableCard')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDisableCard = () => {
|
const handleDisableCard = () => {
|
||||||
|
if (props.stopDisabled) return
|
||||||
emit('disableCard')
|
emit('disableCard')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleManualDeactivate = () => {
|
|
||||||
emit('manualDeactivate')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设备操作
|
// 设备操作
|
||||||
const handleRebootDevice = () => {
|
const handleRebootDevice = () => {
|
||||||
emit('rebootDevice')
|
emit('rebootDevice')
|
||||||
@@ -758,7 +772,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 绑定卡操作
|
// 绑定卡操作
|
||||||
|
const getBindingStartRemainingText = (card: BindingCard) =>
|
||||||
|
props.getStartRemainingText?.(card.iccid) || ''
|
||||||
|
|
||||||
|
const getBindingStopRemainingText = (card: BindingCard) =>
|
||||||
|
props.getStopRemainingText?.(card.iccid) || ''
|
||||||
|
|
||||||
|
const isBindingStartDisabled = (card: BindingCard) => props.isStartDisabled?.(card.iccid) || false
|
||||||
|
|
||||||
|
const isBindingStopDisabled = (card: BindingCard) => props.isStopDisabled?.(card.iccid) || false
|
||||||
|
|
||||||
|
const getBindingStartButtonText = (card: BindingCard) => {
|
||||||
|
const remainingText = getBindingStartRemainingText(card)
|
||||||
|
return remainingText ? `启用此卡(${remainingText})` : '启用此卡'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBindingStopButtonText = (card: BindingCard) => {
|
||||||
|
const remainingText = getBindingStopRemainingText(card)
|
||||||
|
return remainingText ? `停用此卡(${remainingText})` : '停用此卡'
|
||||||
|
}
|
||||||
|
|
||||||
const handleEnableBindingCard = (card: BindingCard) => {
|
const handleEnableBindingCard = (card: BindingCard) => {
|
||||||
|
if (isBindingStartDisabled(card)) return
|
||||||
ElMessageBox.confirm(`确定要启用此卡(${card.iccid})吗?`, '启用确认', {
|
ElMessageBox.confirm(`确定要启用此卡(${card.iccid})吗?`, '启用确认', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
@@ -771,6 +806,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleDisableBindingCard = (card: BindingCard) => {
|
const handleDisableBindingCard = (card: BindingCard) => {
|
||||||
|
if (isBindingStopDisabled(card)) return
|
||||||
ElMessageBox.confirm(`确定要停用此卡(${card.iccid})吗?`, '停用确认', {
|
ElMessageBox.confirm(`确定要停用此卡(${card.iccid})吗?`, '停用确认', {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
|
|||||||
@@ -69,6 +69,10 @@ export function useAssetOperations(cardInfo: Ref<any>, refreshAssetFn?: () => Pr
|
|||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error !== 'cancel') {
|
if (error !== 'cancel') {
|
||||||
|
if (error instanceof FrontendRateLimitError) {
|
||||||
|
ElMessage.warning(`操作过于频繁,请${formatRemainingTime(error.remainingMs)}后再试`)
|
||||||
|
return
|
||||||
|
}
|
||||||
console.error('停用失败:', error)
|
console.error('停用失败:', error)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -20,6 +20,14 @@
|
|||||||
:device-realtime="deviceRealtime"
|
:device-realtime="deviceRealtime"
|
||||||
v-model:polling-enabled="pollingEnabled"
|
v-model:polling-enabled="pollingEnabled"
|
||||||
:realtime-loading="realtimeLoading"
|
:realtime-loading="realtimeLoading"
|
||||||
|
:start-disabled="startDisabled"
|
||||||
|
:stop-disabled="stopDisabled"
|
||||||
|
:start-remaining-text="startRemainingText"
|
||||||
|
:stop-remaining-text="stopRemainingText"
|
||||||
|
:get-start-remaining-text="getStartRemainingText"
|
||||||
|
:get-stop-remaining-text="getStopRemainingText"
|
||||||
|
:is-start-disabled="isStartActionDisabled"
|
||||||
|
:is-stop-disabled="isStopActionDisabled"
|
||||||
@enable-card="handleEnableCard"
|
@enable-card="handleEnableCard"
|
||||||
@disable-card="handleDisableCard"
|
@disable-card="handleDisableCard"
|
||||||
@manual-deactivate="handleManualDeactivate"
|
@manual-deactivate="handleManualDeactivate"
|
||||||
@@ -176,7 +184,8 @@
|
|||||||
import {
|
import {
|
||||||
formatRemainingTime,
|
formatRemainingTime,
|
||||||
FrontendRateLimitError,
|
FrontendRateLimitError,
|
||||||
getAssetActionRateLimitState
|
getAssetActionRateLimitState,
|
||||||
|
type AssetRateLimitedAction
|
||||||
} from '@/utils/business/apiRateLimit'
|
} from '@/utils/business/apiRateLimit'
|
||||||
|
|
||||||
// 引入子组件
|
// 引入子组件
|
||||||
@@ -248,6 +257,8 @@
|
|||||||
|
|
||||||
// ========== 资产操作方法(使用 composable) ==========
|
// ========== 资产操作方法(使用 composable) ==========
|
||||||
const {
|
const {
|
||||||
|
enableCardLoading,
|
||||||
|
disableCardLoading,
|
||||||
enableCard: enableCardOp,
|
enableCard: enableCardOp,
|
||||||
disableCard: disableCardOp,
|
disableCard: disableCardOp,
|
||||||
manualDeactivateCard: manualDeactivateCardOp,
|
manualDeactivateCard: manualDeactivateCardOp,
|
||||||
@@ -342,6 +353,59 @@
|
|||||||
: ''
|
: ''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const startRateLimitState = computed(() => {
|
||||||
|
const identifier = cardInfo.value?.iccid || cardInfo.value?.identifier
|
||||||
|
if (!identifier) {
|
||||||
|
return {
|
||||||
|
limited: false,
|
||||||
|
remainingMs: 0,
|
||||||
|
retryAt: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getAssetActionRateLimitState('start', identifier, rateLimitNow.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const stopRateLimitState = computed(() => {
|
||||||
|
const identifier = cardInfo.value?.iccid || cardInfo.value?.identifier
|
||||||
|
if (!identifier) {
|
||||||
|
return {
|
||||||
|
limited: false,
|
||||||
|
remainingMs: 0,
|
||||||
|
retryAt: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getAssetActionRateLimitState('stop', identifier, rateLimitNow.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const startDisabled = computed(() => enableCardLoading.value || startRateLimitState.value.limited)
|
||||||
|
const stopDisabled = computed(() => disableCardLoading.value || stopRateLimitState.value.limited)
|
||||||
|
const startRemainingText = computed(() =>
|
||||||
|
startRateLimitState.value.limited
|
||||||
|
? formatRemainingTime(startRateLimitState.value.remainingMs)
|
||||||
|
: ''
|
||||||
|
)
|
||||||
|
const stopRemainingText = computed(() =>
|
||||||
|
stopRateLimitState.value.limited
|
||||||
|
? formatRemainingTime(stopRateLimitState.value.remainingMs)
|
||||||
|
: ''
|
||||||
|
)
|
||||||
|
|
||||||
|
const getActionRemainingText = (action: AssetRateLimitedAction, identifier?: string) => {
|
||||||
|
if (!identifier) return ''
|
||||||
|
const state = getAssetActionRateLimitState(action, identifier, rateLimitNow.value)
|
||||||
|
return state.limited ? formatRemainingTime(state.remainingMs) : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const isActionDisabled = (action: AssetRateLimitedAction, identifier?: string) => {
|
||||||
|
if (!identifier) return false
|
||||||
|
return getAssetActionRateLimitState(action, identifier, rateLimitNow.value).limited
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStartRemainingText = (identifier: string) => getActionRemainingText('start', identifier)
|
||||||
|
const getStopRemainingText = (identifier: string) => getActionRemainingText('stop', identifier)
|
||||||
|
const isStartActionDisabled = (identifier: string) => isActionDisabled('start', identifier)
|
||||||
|
const isStopActionDisabled = (identifier: string) => isActionDisabled('stop', identifier)
|
||||||
|
|
||||||
const updateRateLimitNow = () => {
|
const updateRateLimitNow = () => {
|
||||||
rateLimitNow.value = Date.now()
|
rateLimitNow.value = Date.now()
|
||||||
}
|
}
|
||||||
@@ -399,14 +463,24 @@
|
|||||||
* IoT卡操作 - 启用卡
|
* IoT卡操作 - 启用卡
|
||||||
*/
|
*/
|
||||||
const handleEnableCard = async () => {
|
const handleEnableCard = async () => {
|
||||||
|
if (startDisabled.value) return
|
||||||
|
try {
|
||||||
await enableCardOp()
|
await enableCardOp()
|
||||||
|
} finally {
|
||||||
|
updateRateLimitNow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IoT卡操作 - 停用卡
|
* IoT卡操作 - 停用卡
|
||||||
*/
|
*/
|
||||||
const handleDisableCard = async () => {
|
const handleDisableCard = async () => {
|
||||||
|
if (stopDisabled.value) return
|
||||||
|
try {
|
||||||
await disableCardOp()
|
await disableCardOp()
|
||||||
|
} finally {
|
||||||
|
updateRateLimitNow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -550,6 +624,7 @@
|
|||||||
*/
|
*/
|
||||||
const handleEnableBindingCard = async (payload: { card: any }) => {
|
const handleEnableBindingCard = async (payload: { card: any }) => {
|
||||||
const { card } = payload
|
const { card } = payload
|
||||||
|
if (isStartActionDisabled(card.iccid)) return
|
||||||
try {
|
try {
|
||||||
const { AssetService } = await import('@/api/modules')
|
const { AssetService } = await import('@/api/modules')
|
||||||
await AssetService.startAsset(card.iccid)
|
await AssetService.startAsset(card.iccid)
|
||||||
@@ -562,6 +637,8 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.error('启用绑定卡失败:', error)
|
console.error('启用绑定卡失败:', error)
|
||||||
|
} finally {
|
||||||
|
updateRateLimitNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,6 +647,7 @@
|
|||||||
*/
|
*/
|
||||||
const handleDisableBindingCard = async (payload: { card: any }) => {
|
const handleDisableBindingCard = async (payload: { card: any }) => {
|
||||||
const { card } = payload
|
const { card } = payload
|
||||||
|
if (isStopActionDisabled(card.iccid)) return
|
||||||
try {
|
try {
|
||||||
const { AssetService } = await import('@/api/modules')
|
const { AssetService } = await import('@/api/modules')
|
||||||
await AssetService.stopAsset(card.iccid)
|
await AssetService.stopAsset(card.iccid)
|
||||||
@@ -577,7 +655,13 @@
|
|||||||
if (cardInfo.value)
|
if (cardInfo.value)
|
||||||
await loadRealtimeStatus(cardInfo.value.identifier, cardInfo.value.asset_type)
|
await loadRealtimeStatus(cardInfo.value.identifier, cardInfo.value.asset_type)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
if (error instanceof FrontendRateLimitError) {
|
||||||
|
ElMessage.warning(`操作过于频繁,请${formatRemainingTime(error.remainingMs)}后再试`)
|
||||||
|
return
|
||||||
|
}
|
||||||
console.error('停用绑定卡失败:', error)
|
console.error('停用绑定卡失败:', error)
|
||||||
|
} finally {
|
||||||
|
updateRateLimitNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2920,6 +2920,10 @@
|
|||||||
await getTableData()
|
await getTableData()
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
if (error instanceof FrontendRateLimitError) {
|
||||||
|
ElMessage.warning(`操作过于频繁,请${formatRemainingTime(error.remainingMs)}后再试`)
|
||||||
|
return
|
||||||
|
}
|
||||||
console.error('停用此卡失败:', error)
|
console.error('停用此卡失败:', error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user