fix(web): suppress duplicate toasts when auth session expires
Desktop Client Build / Resolve Build Metadata (push) Successful in 32s
Frontend CI / Frontend (push) Successful in 4m4s
Desktop Client Build / Publish Client Artifacts to NAS (push) Has been cancelled
Desktop Client Build / Build Desktop Client (push) Has been cancelled
Desktop Client Build / Resolve Build Metadata (push) Successful in 32s
Frontend CI / Frontend (push) Successful in 4m4s
Desktop Client Build / Publish Client Artifacts to NAS (push) Has been cancelled
Desktop Client Build / Build Desktop Client (push) Has been cancelled
Mark 401/refresh-failure errors as handled so per-view catch blocks fall through silently while a single global "登录已过期" warning is shown. Centralize ops-web error rendering via showOpsError. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -130,7 +130,8 @@ import { message } from 'ant-design-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { OpsApiError, http } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
import { http } from '@/lib/http'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
interface AccountRow {
|
||||
@@ -211,7 +212,7 @@ async function reload() {
|
||||
rows.value = result.items
|
||||
total.value = result.total
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -266,7 +267,7 @@ async function submitCreate() {
|
||||
resetCreate()
|
||||
void reload()
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
createLoading.value = false
|
||||
}
|
||||
@@ -297,7 +298,7 @@ async function submitResetPassword() {
|
||||
message.success('重置成功')
|
||||
resetOpen.value = false
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
resetLoading.value = false
|
||||
}
|
||||
@@ -310,7 +311,7 @@ async function toggleStatus(row: AccountRow) {
|
||||
message.success(next === 'active' ? '已启用' : '已停用')
|
||||
void reload()
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -302,7 +302,8 @@ import { message } from 'ant-design-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { OpsApiError, http } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
import { http } from '@/lib/http'
|
||||
|
||||
interface AdminUserRow {
|
||||
id: number
|
||||
@@ -447,7 +448,7 @@ async function reload() {
|
||||
rows.value = result.items
|
||||
total.value = result.total
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -462,7 +463,7 @@ async function loadMeta() {
|
||||
plans.value = planResult
|
||||
roles.value = roleResult
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,7 +527,7 @@ async function submitCreate() {
|
||||
resetCreate()
|
||||
void reload()
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
createLoading.value = false
|
||||
}
|
||||
@@ -608,7 +609,7 @@ async function submitEdit() {
|
||||
editOpen.value = false
|
||||
void reload()
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
editLoading.value = false
|
||||
}
|
||||
@@ -624,7 +625,7 @@ async function resetPlanUsage(row: AdminUserRow) {
|
||||
rows.value = rows.value.map((item) => (item.id === row.id ? result.user : item))
|
||||
void reload()
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
resetUsageLoadingId.value = null
|
||||
}
|
||||
@@ -644,7 +645,7 @@ async function resetLoginLock(row: AdminUserRow) {
|
||||
message.warning('未发现登录锁 Redis key,请检查 ops-api 与 admin-web 使用的 Redis 地址和 DB 是否一致')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
resetLoginLockLoadingId.value = null
|
||||
}
|
||||
@@ -675,7 +676,7 @@ async function submitResetPassword() {
|
||||
message.success('密码已重置')
|
||||
resetOpen.value = false
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
resetLoading.value = false
|
||||
}
|
||||
@@ -711,7 +712,7 @@ async function submitKol() {
|
||||
kolOpen.value = false
|
||||
void reload()
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
kolLoading.value = false
|
||||
}
|
||||
@@ -724,7 +725,7 @@ async function toggleStatus(row: AdminUserRow) {
|
||||
message.success(next === 'active' ? '已启用' : '已停用')
|
||||
void reload()
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,8 @@ import { message } from 'ant-design-vue'
|
||||
import dayjs, { type Dayjs } from 'dayjs'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
|
||||
import { OpsApiError, http } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
import { http } from '@/lib/http'
|
||||
|
||||
interface AuditRow {
|
||||
id: number
|
||||
@@ -161,7 +162,7 @@ async function reload() {
|
||||
rows.value = result.items
|
||||
total.value = result.total
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@@ -168,7 +168,8 @@ import { message } from 'ant-design-vue'
|
||||
import dayjs, { type Dayjs } from 'dayjs'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { OpsApiError, http } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
import { http } from '@/lib/http'
|
||||
|
||||
interface JobSummary {
|
||||
uid: string
|
||||
@@ -429,11 +430,7 @@ function formatJSON(value: unknown): string {
|
||||
}
|
||||
|
||||
function showError(error: unknown) {
|
||||
if (error instanceof OpsApiError) {
|
||||
message.error(error.detail || error.message)
|
||||
} else {
|
||||
message.error('请求失败')
|
||||
}
|
||||
showOpsError(error)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -232,7 +232,8 @@ import { message } from 'ant-design-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { OpsApiError, http } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
import { http } from '@/lib/http'
|
||||
|
||||
interface KolSubscriptionRow {
|
||||
id: number
|
||||
@@ -349,7 +350,7 @@ async function reload() {
|
||||
rows.value = result.items
|
||||
total.value = result.total
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -363,7 +364,7 @@ async function loadPackages(keyword = '') {
|
||||
size: 50,
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
packageLoading.value = false
|
||||
}
|
||||
@@ -437,7 +438,7 @@ async function submitManualBind() {
|
||||
page.value = 1
|
||||
void reload()
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
manualBindLoading.value = false
|
||||
}
|
||||
@@ -472,7 +473,7 @@ async function submitApprove() {
|
||||
approveOpen.value = false
|
||||
void reload()
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
approveLoading.value = false
|
||||
}
|
||||
@@ -487,7 +488,7 @@ async function revoke(row: KolSubscriptionRow) {
|
||||
message.success('订阅已撤销')
|
||||
void reload()
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) message.error(error.detail || error.message)
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
revokeLoadingId.value = null
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
import { OpsApiError, http } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
import { http } from '@/lib/http'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const auth = useAuthStore()
|
||||
@@ -71,9 +72,7 @@ async function onSubmit() {
|
||||
// router redirect handled by 401 interceptor or next route guard
|
||||
if (typeof window !== 'undefined') window.location.href = '/login'
|
||||
} catch (error) {
|
||||
if (error instanceof OpsApiError) {
|
||||
message.error(error.detail || error.message)
|
||||
}
|
||||
showOpsError(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@@ -143,7 +143,8 @@ import { message } from 'ant-design-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { OpsApiError, http } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
import { http } from '@/lib/http'
|
||||
|
||||
interface SiteDomainMappingRow {
|
||||
id: number
|
||||
@@ -342,9 +343,7 @@ function formatDate(value: string | null | undefined): string {
|
||||
}
|
||||
|
||||
function handleError(error: unknown) {
|
||||
if (error instanceof OpsApiError) {
|
||||
message.error(error.detail || error.message)
|
||||
}
|
||||
showOpsError(error)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -136,7 +136,7 @@ import {
|
||||
platformLabel,
|
||||
} from '@/lib/compliance-display'
|
||||
import { complianceApi, type OpsComplianceDictionary } from '@/lib/compliance'
|
||||
import { OpsApiError } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -220,9 +220,7 @@ function formatDate(value: string): string {
|
||||
}
|
||||
|
||||
function showError(error: unknown) {
|
||||
if (error instanceof OpsApiError) {
|
||||
message.error(error.detail || error.message)
|
||||
}
|
||||
showOpsError(error)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -233,7 +233,7 @@ import {
|
||||
type OpsComplianceDictionary,
|
||||
type OpsComplianceTerm,
|
||||
} from '@/lib/compliance'
|
||||
import { OpsApiError } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
|
||||
type MatchType = 'exact' | 'regex'
|
||||
|
||||
@@ -514,9 +514,7 @@ function resetTermForm() {
|
||||
}
|
||||
|
||||
function showError(error: unknown) {
|
||||
if (error instanceof OpsApiError) {
|
||||
message.error(error.detail || error.message)
|
||||
}
|
||||
showOpsError(error)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -134,7 +134,7 @@ import {
|
||||
sourceLabel,
|
||||
} from '@/lib/compliance-display'
|
||||
import { complianceApi } from '@/lib/compliance'
|
||||
import { OpsApiError } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -195,9 +195,7 @@ function formatDate(value: string): string {
|
||||
}
|
||||
|
||||
function showError(error: unknown) {
|
||||
if (error instanceof OpsApiError) {
|
||||
message.error(error.detail || error.message)
|
||||
}
|
||||
showOpsError(error)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -89,7 +89,6 @@
|
||||
import type { ComplianceManualReview } from '@geo/shared-types'
|
||||
import { ReloadOutlined } from '@ant-design/icons-vue'
|
||||
import type { TableColumnsType, TablePaginationConfig } from 'ant-design-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -101,7 +100,7 @@ import {
|
||||
reviewStatusLabel,
|
||||
} from '@/lib/compliance-display'
|
||||
import { complianceApi } from '@/lib/compliance'
|
||||
import { OpsApiError } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -166,9 +165,7 @@ function formatDate(value: string): string {
|
||||
}
|
||||
|
||||
function showError(error: unknown) {
|
||||
if (error instanceof OpsApiError) {
|
||||
message.error(error.detail || error.message)
|
||||
}
|
||||
showOpsError(error)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -123,7 +123,7 @@ import {
|
||||
type CompliancePolicy,
|
||||
type OpsComplianceDictionary,
|
||||
} from '@/lib/compliance'
|
||||
import { OpsApiError } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
|
||||
const policy = ref<CompliancePolicy | null>(null)
|
||||
const dictionaries = ref<OpsComplianceDictionary[]>([])
|
||||
@@ -220,9 +220,7 @@ function formatDate(value: string): string {
|
||||
}
|
||||
|
||||
function showError(error: unknown) {
|
||||
if (error instanceof OpsApiError) {
|
||||
message.error(error.detail || error.message)
|
||||
}
|
||||
showOpsError(error)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
import type { ComplianceCheckResult } from '@geo/shared-types'
|
||||
import { ReloadOutlined } from '@ant-design/icons-vue'
|
||||
import type { TableColumnsType, TablePaginationConfig } from 'ant-design-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
@@ -114,7 +113,7 @@ import {
|
||||
reviewStatusLabel,
|
||||
} from '@/lib/compliance-display'
|
||||
import { complianceApi } from '@/lib/compliance'
|
||||
import { OpsApiError } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
|
||||
const rows = ref<ComplianceCheckResult[]>([])
|
||||
const loading = ref(false)
|
||||
@@ -187,9 +186,7 @@ function formatDate(value: string): string {
|
||||
}
|
||||
|
||||
function showError(error: unknown) {
|
||||
if (error instanceof OpsApiError) {
|
||||
message.error(error.detail || error.message)
|
||||
}
|
||||
showOpsError(error)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -74,12 +74,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ReloadOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
import { levelColor, levelLabel, sourceLabel } from '@/lib/compliance-display'
|
||||
import { complianceApi, type ComplianceStats } from '@/lib/compliance'
|
||||
import { OpsApiError } from '@/lib/http'
|
||||
import { showOpsError } from '@/lib/errors'
|
||||
|
||||
const stats = ref<ComplianceStats | null>(null)
|
||||
const loading = ref(false)
|
||||
@@ -105,9 +104,7 @@ function toRows(value?: Record<string, number>) {
|
||||
}
|
||||
|
||||
function showError(error: unknown) {
|
||||
if (error instanceof OpsApiError) {
|
||||
message.error(error.detail || error.message)
|
||||
}
|
||||
showOpsError(error)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
Reference in New Issue
Block a user