feat: add i18n for Risk Control pages
- Add nav.risk key (en: Risk Control, zh: 风控中心) - Add admin.risk.* keys for all UI text in view and components - Replace all hardcoded English strings with t() calls
This commit is contained in:
parent
52ad76e6a4
commit
3333307ec1
@ -17,7 +17,7 @@
|
||||
<div class="p-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Account Risk Detail</h2>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">{{ t('admin.risk.drawer.title') }}</h2>
|
||||
<button
|
||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200"
|
||||
@click="$emit('close')"
|
||||
@ -29,28 +29,28 @@
|
||||
</div>
|
||||
|
||||
<template v-if="riskStore.loading && !detail">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">Loading…</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">{{ t('admin.risk.loading') }}</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="detail">
|
||||
<!-- Basic info -->
|
||||
<div class="space-y-2 mb-6">
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">Email</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('admin.risk.drawer.email') }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ detail.email }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">Platform</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('admin.risk.drawer.platform') }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ detail.platform }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">Risk Level</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('admin.risk.drawer.riskLevel') }}</span>
|
||||
<span :class="levelClass(detail.risk_level)" class="font-semibold capitalize">
|
||||
{{ detail.risk_level }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">Risk Score</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('admin.risk.drawer.riskScore') }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">
|
||||
{{ (detail.risk_score * 100).toFixed(1) }}%
|
||||
</span>
|
||||
@ -67,7 +67,7 @@
|
||||
|
||||
<!-- Override form -->
|
||||
<div class="border-t border-gray-200 dark:border-gray-700 pt-4 mb-6">
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white mb-3">Override Risk Level</h3>
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white mb-3">{{ t('admin.risk.drawer.overrideTitle') }}</h3>
|
||||
<div class="space-y-2">
|
||||
<select
|
||||
v-model="overrideLevel"
|
||||
@ -80,7 +80,7 @@
|
||||
<input
|
||||
v-model="overrideReason"
|
||||
type="text"
|
||||
placeholder="Reason (required)"
|
||||
:placeholder="t('admin.risk.drawer.overrideReason')"
|
||||
class="w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-sm text-gray-900 dark:text-white px-3 py-1.5"
|
||||
/>
|
||||
<button
|
||||
@ -88,14 +88,14 @@
|
||||
class="w-full rounded bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
|
||||
@click="onOverride"
|
||||
>
|
||||
{{ overriding ? 'Saving…' : 'Apply Override' }}
|
||||
{{ overriding ? t('admin.risk.drawer.applying') : t('admin.risk.drawer.applyOverride') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 24h behavior trend -->
|
||||
<div class="border-t border-gray-200 dark:border-gray-700 pt-4">
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white mb-2">24h Behavior</h3>
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white mb-2">{{ t('admin.risk.drawer.behavior24h') }}</h3>
|
||||
<div class="h-20">
|
||||
<RiskTrendChart :hours="detail.behavior_24h ?? []" />
|
||||
</div>
|
||||
@ -108,6 +108,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRiskStore } from '@/stores/risk'
|
||||
import RiskTrendChart from './RiskTrendChart.vue'
|
||||
|
||||
@ -122,6 +123,7 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const riskStore = useRiskStore()
|
||||
const { t } = useI18n()
|
||||
const detail = ref(riskStore.accountDetail)
|
||||
const overrideLevel = ref('low')
|
||||
const overrideReason = ref('')
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||
<div class="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Total Monitored</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">{{ t('admin.risk.summaryCards.totalMonitored') }}</p>
|
||||
<p class="mt-1 text-2xl font-semibold text-gray-900 dark:text-white">
|
||||
{{ summary?.total_monitored ?? '—' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20 p-4">
|
||||
<p class="text-xs text-red-600 dark:text-red-400 uppercase tracking-wide">High Risk</p>
|
||||
<p class="text-xs text-red-600 dark:text-red-400 uppercase tracking-wide">{{ t('admin.risk.summaryCards.highRisk') }}</p>
|
||||
<p class="mt-1 text-2xl font-semibold text-red-700 dark:text-red-300">
|
||||
{{ summary?.high_risk_count ?? '—' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-yellow-200 dark:border-yellow-800 bg-yellow-50 dark:bg-yellow-900/20 p-4">
|
||||
<p class="text-xs text-yellow-600 dark:text-yellow-400 uppercase tracking-wide">Medium Risk</p>
|
||||
<p class="text-xs text-yellow-600 dark:text-yellow-400 uppercase tracking-wide">{{ t('admin.risk.summaryCards.mediumRisk') }}</p>
|
||||
<p class="mt-1 text-2xl font-semibold text-yellow-700 dark:text-yellow-300">
|
||||
{{ summary?.medium_risk_count ?? '—' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">Blocked</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">{{ t('admin.risk.summaryCards.blocked') }}</p>
|
||||
<p class="mt-1 text-2xl font-semibold text-gray-900 dark:text-white">
|
||||
{{ summary?.blocked_count ?? '—' }}
|
||||
</p>
|
||||
@ -28,9 +28,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { RiskSummary } from '@/api/admin/risk'
|
||||
|
||||
defineProps<{
|
||||
summary: RiskSummary | null
|
||||
}>()
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
<template>
|
||||
<div class="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-4">
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white mb-3">Risk System Settings</h3>
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white mb-3">{{ t('admin.risk.settings.title') }}</h3>
|
||||
<form class="space-y-3" @submit.prevent="onSave">
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">Phase</label>
|
||||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">{{ t('admin.risk.settings.phase') }}</label>
|
||||
<select
|
||||
v-model="form.phase"
|
||||
class="w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-sm text-gray-900 dark:text-white px-3 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
>
|
||||
<option value="off">Off</option>
|
||||
<option value="observe">Observe</option>
|
||||
<option value="enforce">Enforce</option>
|
||||
<option value="observe">{{ t('admin.risk.settings.phaseObserve') }}</option>
|
||||
<option value="enforce">{{ t('admin.risk.settings.phaseEnforce') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">
|
||||
Medium Threshold ({{ form.medium_threshold }})
|
||||
{{ t('admin.risk.settings.mediumThreshold', { v: form.medium_threshold }) }}
|
||||
</label>
|
||||
<input
|
||||
v-model.number="form.medium_threshold"
|
||||
@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">
|
||||
High Threshold ({{ form.high_threshold }})
|
||||
{{ t('admin.risk.settings.highThreshold', { v: form.high_threshold }) }}
|
||||
</label>
|
||||
<input
|
||||
v-model.number="form.high_threshold"
|
||||
@ -46,22 +46,23 @@
|
||||
type="checkbox"
|
||||
class="rounded border-gray-300 text-blue-600"
|
||||
/>
|
||||
<label for="risk-enabled" class="text-xs text-gray-600 dark:text-gray-400">Enabled</label>
|
||||
<label for="risk-enabled" class="text-xs text-gray-600 dark:text-gray-400">{{ t('admin.risk.settings.enabled') }}</label>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="saving"
|
||||
class="w-full rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{{ saving ? 'Saving…' : 'Save Settings' }}
|
||||
{{ saving ? t('admin.risk.settings.saving') : t('admin.risk.settings.save') }}
|
||||
</button>
|
||||
<p v-if="saved" class="text-xs text-green-600 dark:text-green-400 text-center">Saved</p>
|
||||
<p v-if="saved" class="text-xs text-green-600 dark:text-green-400 text-center">{{ t('admin.risk.settings.saved') }}</p>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRiskStore } from '@/stores/risk'
|
||||
import type { RiskSettings } from '@/api/admin/risk'
|
||||
|
||||
@ -70,6 +71,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const riskStore = useRiskStore()
|
||||
const { t } = useI18n()
|
||||
const saving = ref(false)
|
||||
const saved = ref(false)
|
||||
|
||||
|
||||
@ -591,7 +591,7 @@ const adminNavItems = computed((): NavItem[] => {
|
||||
{ path: '/admin/redeem', label: t('nav.redeemCodes'), icon: TicketIcon, hideInSimpleMode: true },
|
||||
{ path: '/admin/promo-codes', label: t('nav.promoCodes'), icon: GiftIcon, hideInSimpleMode: true },
|
||||
{ path: '/admin/usage', label: t('nav.usage'), icon: ChartIcon },
|
||||
{ path: '/admin/risk', label: 'Risk Control', icon: ShieldExclamationIcon }
|
||||
{ path: '/admin/risk', label: t('nav.risk'), icon: ShieldExclamationIcon }
|
||||
]
|
||||
|
||||
// 简单模式下,在系统设置前插入 API密钥
|
||||
|
||||
@ -352,7 +352,8 @@ export default {
|
||||
mySubscriptions: 'My Subscriptions',
|
||||
buySubscription: 'Recharge / Subscription',
|
||||
docs: 'Docs',
|
||||
sora: 'Sora Studio'
|
||||
sora: 'Sora Studio',
|
||||
risk: 'Risk Control'
|
||||
},
|
||||
|
||||
// Auth
|
||||
@ -4630,6 +4631,57 @@ export default {
|
||||
loadFailed: 'Failed to load profiles',
|
||||
saveFailed: 'Failed to save profile',
|
||||
deleteFailed: 'Failed to delete profile'
|
||||
},
|
||||
|
||||
// Risk Control
|
||||
risk: {
|
||||
title: 'Risk Control',
|
||||
allLevels: 'All Levels',
|
||||
allPlatforms: 'All Platforms',
|
||||
refresh: 'Refresh',
|
||||
email: 'Email',
|
||||
platform: 'Platform',
|
||||
level: 'Level',
|
||||
score: 'Score',
|
||||
detail: 'Detail',
|
||||
loading: 'Loading…',
|
||||
noAccounts: 'No accounts found',
|
||||
total: '{n} total',
|
||||
prev: 'Prev',
|
||||
next: 'Next',
|
||||
overridden: '(overridden)',
|
||||
riskDistribution: 'Risk Distribution',
|
||||
summaryCards: {
|
||||
totalMonitored: 'Total Monitored',
|
||||
highRisk: 'High Risk',
|
||||
mediumRisk: 'Medium Risk',
|
||||
blocked: 'Blocked'
|
||||
},
|
||||
settings: {
|
||||
title: 'Risk System Settings',
|
||||
phase: 'Phase',
|
||||
phaseOff: 'Off',
|
||||
phaseObserve: 'Observe',
|
||||
phaseEnforce: 'Enforce',
|
||||
mediumThreshold: 'Medium Threshold ({v})',
|
||||
highThreshold: 'High Threshold ({v})',
|
||||
enabled: 'Enabled',
|
||||
save: 'Save Settings',
|
||||
saving: 'Saving…',
|
||||
saved: 'Saved'
|
||||
},
|
||||
drawer: {
|
||||
title: 'Account Risk Detail',
|
||||
email: 'Email',
|
||||
platform: 'Platform',
|
||||
riskLevel: 'Risk Level',
|
||||
riskScore: 'Risk Score',
|
||||
overrideTitle: 'Override Risk Level',
|
||||
overrideReason: 'Reason (required)',
|
||||
applyOverride: 'Apply Override',
|
||||
applying: 'Saving…',
|
||||
behavior24h: '24h Behavior'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -352,7 +352,8 @@ export default {
|
||||
mySubscriptions: '我的订阅',
|
||||
buySubscription: '充值/订阅',
|
||||
docs: '文档',
|
||||
sora: 'Sora 创作'
|
||||
sora: 'Sora 创作',
|
||||
risk: '风控中心'
|
||||
},
|
||||
|
||||
// Auth
|
||||
@ -4794,6 +4795,57 @@ export default {
|
||||
loadFailed: '加载模板失败',
|
||||
saveFailed: '保存模板失败',
|
||||
deleteFailed: '删除模板失败'
|
||||
},
|
||||
|
||||
// 风控中心
|
||||
risk: {
|
||||
title: '风控中心',
|
||||
allLevels: '全部风险等级',
|
||||
allPlatforms: '全部平台',
|
||||
refresh: '刷新',
|
||||
email: '邮箱',
|
||||
platform: '平台',
|
||||
level: '风险等级',
|
||||
score: '风险分',
|
||||
detail: '详情',
|
||||
loading: '加载中…',
|
||||
noAccounts: '暂无账号',
|
||||
total: '共 {n} 条',
|
||||
prev: '上一页',
|
||||
next: '下一页',
|
||||
overridden: '(已覆盖)',
|
||||
riskDistribution: '风险分布',
|
||||
summaryCards: {
|
||||
totalMonitored: '监控总数',
|
||||
highRisk: '高风险',
|
||||
mediumRisk: '中风险',
|
||||
blocked: '已封禁'
|
||||
},
|
||||
settings: {
|
||||
title: '风控系统设置',
|
||||
phase: '运行阶段',
|
||||
phaseOff: '关闭',
|
||||
phaseObserve: '观察',
|
||||
phaseEnforce: '执法',
|
||||
mediumThreshold: '中风险阈值({v})',
|
||||
highThreshold: '高风险阈值({v})',
|
||||
enabled: '启用',
|
||||
save: '保存设置',
|
||||
saving: '保存中…',
|
||||
saved: '已保存'
|
||||
},
|
||||
drawer: {
|
||||
title: '账号风控详情',
|
||||
email: '邮箱',
|
||||
platform: '平台',
|
||||
riskLevel: '风险等级',
|
||||
riskScore: '风险分',
|
||||
overrideTitle: '覆盖风险等级',
|
||||
overrideReason: '原因(必填)',
|
||||
applyOverride: '应用覆盖',
|
||||
applying: '保存中…',
|
||||
behavior24h: '24小时行为'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
class="rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-sm text-gray-900 dark:text-white px-3 py-1.5"
|
||||
@change="applyFilter"
|
||||
>
|
||||
<option value="">All Levels</option>
|
||||
<option value="">{{ t('admin.risk.allLevels') }}</option>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
@ -24,7 +24,7 @@
|
||||
class="rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-sm text-gray-900 dark:text-white px-3 py-1.5"
|
||||
@change="applyFilter"
|
||||
>
|
||||
<option value="">All Platforms</option>
|
||||
<option value="">{{ t('admin.risk.allPlatforms') }}</option>
|
||||
<option value="claude">Claude</option>
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value="gemini">Gemini</option>
|
||||
@ -34,7 +34,7 @@
|
||||
class="ml-auto rounded bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700"
|
||||
@click="applyFilter"
|
||||
>
|
||||
Refresh
|
||||
{{ t('admin.risk.refresh') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -43,10 +43,10 @@
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700/50">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Email</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Platform</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Level</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Score</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">{{ t('admin.risk.email') }}</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">{{ t('admin.risk.platform') }}</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">{{ t('admin.risk.level') }}</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">{{ t('admin.risk.score') }}</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -59,7 +59,7 @@
|
||||
>
|
||||
<td class="px-4 py-3 text-gray-900 dark:text-white truncate max-w-[200px]">
|
||||
{{ item.email }}
|
||||
<span v-if="item.is_overridden" class="ml-1 text-xs text-blue-500">(overridden)</span>
|
||||
<span v-if="item.is_overridden" class="ml-1 text-xs text-blue-500">{{ t('admin.risk.overridden') }}</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-600 dark:text-gray-400 capitalize">{{ item.platform }}</td>
|
||||
<td class="px-4 py-3">
|
||||
@ -71,12 +71,12 @@
|
||||
{{ (item.risk_score * 100).toFixed(1) }}%
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<button class="text-blue-600 hover:text-blue-700 text-xs">Detail →</button>
|
||||
<button class="text-blue-600 hover:text-blue-700 text-xs">{{ t('admin.risk.detail') }} →</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!riskStore.accounts?.items?.length">
|
||||
<td colspan="5" class="px-4 py-8 text-center text-gray-400 dark:text-gray-500 text-sm">
|
||||
{{ riskStore.loading ? 'Loading…' : 'No accounts found' }}
|
||||
{{ riskStore.loading ? t('admin.risk.loading') : t('admin.risk.noAccounts') }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@ -88,7 +88,7 @@
|
||||
class="border-t border-gray-200 dark:border-gray-700 px-4 py-3 flex items-center justify-between"
|
||||
>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ riskStore.accounts?.total ?? 0 }} total
|
||||
{{ t('admin.risk.total', { n: riskStore.accounts?.total ?? 0 }) }}
|
||||
</span>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@ -96,14 +96,14 @@
|
||||
class="rounded border border-gray-300 dark:border-gray-600 px-2 py-1 text-xs disabled:opacity-40"
|
||||
@click="changePage(filter.page - 1)"
|
||||
>
|
||||
Prev
|
||||
{{ t('admin.risk.prev') }}
|
||||
</button>
|
||||
<button
|
||||
:disabled="(filter.page * filter.limit) >= (riskStore.accounts?.total ?? 0)"
|
||||
class="rounded border border-gray-300 dark:border-gray-600 px-2 py-1 text-xs disabled:opacity-40"
|
||||
@click="changePage(filter.page + 1)"
|
||||
>
|
||||
Next
|
||||
{{ t('admin.risk.next') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -115,7 +115,7 @@
|
||||
<RiskSystemStatusCard :settings="riskStore.settings" />
|
||||
|
||||
<div class="card p-4">
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white mb-3">Risk Distribution</h3>
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-white mb-3">{{ t('admin.risk.riskDistribution') }}</h3>
|
||||
<RiskDistributionChart :summary="riskStore.summary" />
|
||||
</div>
|
||||
</div>
|
||||
@ -134,6 +134,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRiskStore } from '@/stores/risk'
|
||||
import AppLayout from '@/components/layout/AppLayout.vue'
|
||||
import RiskSummaryCards from '@/components/admin/risk/RiskSummaryCards.vue'
|
||||
@ -142,6 +143,7 @@ import RiskSystemStatusCard from '@/components/admin/risk/RiskSystemStatusCard.v
|
||||
import RiskAccountDrawer from '@/components/admin/risk/RiskAccountDrawer.vue'
|
||||
|
||||
const riskStore = useRiskStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const drawerOpen = ref(false)
|
||||
const selectedAccountId = ref<number | null>(null)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user