diff --git a/src/composables/useDashboardStats.ts b/src/composables/useDashboardStats.ts
index 943a37e..8b42d87 100644
--- a/src/composables/useDashboardStats.ts
+++ b/src/composables/useDashboardStats.ts
@@ -1,6 +1,8 @@
import { computed, ref, reactive } from 'vue'
import { StatsService } from '@/services'
import type {
+ DashboardActivitiesParams,
+ DashboardActivityOption,
DashboardActivity,
DashboardActivityViewItem,
DashboardData,
@@ -11,8 +13,11 @@ import { formatFileSize, getErrorMessage } from '@/utils/common'
type UseDashboardStatsOptions = {
loadFailedMessage?: string
+ activityLoadFailedMessage?: string
}
+const activityTimelineLimit = 40
+
const emptyDashboardData = (): DashboardViewData => ({
hasExtendedStats: false,
totalFiles: 0,
@@ -93,6 +98,17 @@ const normalizeRecentActivities = (
meta: activity.meta && typeof activity.meta === 'object' ? activity.meta : {}
}))
+const normalizeActivityOptions = (
+ options: DashboardActivityOption[] = []
+): DashboardActivityOption[] =>
+ options
+ .filter((option) => option && option.value)
+ .map((option) => ({
+ value: option.value,
+ label: option.label || option.value,
+ count: toNumber(option.count)
+ }))
+
const healthSummaryKeys: (keyof DashboardHealthSummary)[] = [
'healthAttentionCount',
'healthDangerCount',
@@ -126,9 +142,32 @@ export function useDashboardStats(options: UseDashboardStatsOptions = {}) {
const isLoading = ref(false)
const errorMessage = ref('')
const lastUpdatedAt = ref(null)
+ const isActivityTimelineOpen = ref(false)
+ const isActivityTimelineLoading = ref(false)
+ const activityTimelineError = ref('')
+ const activityTimeline = ref([])
+ const activityTimelineTotal = ref(0)
+ const activityTimelineStoredTotal = ref(0)
+ const activityActionOptions = ref([])
+ const activityTargetTypeOptions = ref([])
+ const activityFilters = reactive({
+ action: '',
+ targetType: '',
+ keyword: ''
+ })
const lastUpdatedText = computed(() =>
lastUpdatedAt.value ? lastUpdatedAt.value.toLocaleString() : '-'
)
+ const hasActivityFilters = computed(() =>
+ Boolean(activityFilters.action || activityFilters.targetType || activityFilters.keyword)
+ )
+
+ const buildActivityRequestParams = (): DashboardActivitiesParams => ({
+ limit: activityTimelineLimit,
+ action: activityFilters.action || undefined,
+ targetType: activityFilters.targetType || undefined,
+ keyword: activityFilters.keyword.trim() || undefined
+ })
const fetchDashboardData = async () => {
isLoading.value = true
@@ -206,11 +245,93 @@ export function useDashboardStats(options: UseDashboardStatsOptions = {}) {
}
}
+ const fetchActivityTimeline = async () => {
+ isActivityTimelineLoading.value = true
+ activityTimelineError.value = ''
+
+ try {
+ const response = await StatsService.getActivities(buildActivityRequestParams())
+ if (!response.detail) {
+ throw new Error('No activity data')
+ }
+
+ const detail = response.detail
+ activityTimeline.value = normalizeRecentActivities(detail.activities ?? detail.items ?? [])
+ activityTimelineTotal.value = toNumber(detail.total)
+ activityTimelineStoredTotal.value = toNumber(detail.storedTotal ?? detail.stored_total)
+ activityActionOptions.value = normalizeActivityOptions(
+ detail.actionOptions ?? detail.action_options ?? []
+ )
+ activityTargetTypeOptions.value = normalizeActivityOptions(
+ detail.targetTypeOptions ?? detail.target_type_options ?? []
+ )
+ } catch (error) {
+ activityTimelineError.value = getErrorMessage(
+ error,
+ options.activityLoadFailedMessage || 'Failed to load activity timeline'
+ )
+ } finally {
+ isActivityTimelineLoading.value = false
+ }
+ }
+
+ const openActivityTimeline = async () => {
+ isActivityTimelineOpen.value = true
+ await fetchActivityTimeline()
+ }
+
+ const closeActivityTimeline = () => {
+ isActivityTimelineOpen.value = false
+ }
+
+ const setActivityActionFilter = async (action: string) => {
+ activityFilters.action = action
+ await fetchActivityTimeline()
+ }
+
+ const setActivityTargetTypeFilter = async (targetType: string) => {
+ activityFilters.targetType = targetType
+ await fetchActivityTimeline()
+ }
+
+ const setActivityKeywordFilter = (keyword: string) => {
+ activityFilters.keyword = keyword
+ }
+
+ const applyActivityFilters = async () => {
+ await fetchActivityTimeline()
+ }
+
+ const resetActivityFilters = async () => {
+ activityFilters.action = ''
+ activityFilters.targetType = ''
+ activityFilters.keyword = ''
+ await fetchActivityTimeline()
+ }
+
return {
+ activityActionOptions,
+ activityFilters,
+ activityTargetTypeOptions,
+ activityTimeline,
+ activityTimelineError,
+ activityTimelineStoredTotal,
+ activityTimelineTotal,
+ applyActivityFilters,
+ closeActivityTimeline,
dashboardData,
errorMessage,
fetchDashboardData,
+ fetchActivityTimeline,
+ hasActivityFilters,
+ isActivityTimelineLoading,
+ isActivityTimelineOpen,
isLoading,
- lastUpdatedText
+ lastUpdatedText,
+ openActivityTimeline,
+ resetActivityFilters,
+ setActivityActionFilter,
+ setActivityKeywordFilter,
+ setActivityTargetTypeFilter
}
}
diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts
index 18684d6..76f62ea 100644
--- a/src/i18n/locales/en-US.ts
+++ b/src/i18n/locales/en-US.ts
@@ -90,6 +90,22 @@ export default {
textType: 'Text',
recentActivities: 'Recent Activity',
recentActivitiesDesc: 'Key admin changes for quick operational traceability.',
+ viewAllActivities: 'View all',
+ activityTimelineTitle: 'Activity Timeline',
+ activityTimelineFiltered: 'Current Results',
+ activityTimelineStoredTotal: 'Retained Records',
+ activityKeyword: 'Keyword',
+ activityKeywordPlaceholder: 'Search target, action, or ID',
+ activityActionFilter: 'Action',
+ activityAllActions: 'All actions',
+ activityTargetTypeFilter: 'Target type',
+ activityAllTargets: 'All targets',
+ activityResetFilters: 'Reset',
+ activityRefresh: 'Refresh',
+ activityApplyFilters: 'Apply',
+ activityLoading: 'Loading activity records...',
+ activityNoFiltered: 'No matching activity records',
+ activityLoadFailed: 'Failed to load activity timeline',
noActivities: 'No activity yet',
activityUnknownTarget: 'Unnamed target',
activityDescription: 'Target: {target}',
@@ -109,6 +125,13 @@ export default {
'local_file.delete': 'Deleted local file',
'local_file.share': 'Shared local file'
},
+ activityTargetTypes: {
+ file: 'File',
+ view_preset: 'View preset',
+ config: 'System settings',
+ local_file: 'Local file',
+ system: 'System'
+ },
recentFiles: 'Recent Shares',
recentFilesDesc: 'Recently created share records for quick status checks.',
available: 'Available',
@@ -852,6 +875,22 @@ export default {
textType: 'Text',
recentActivities: 'Recent Activity',
recentActivitiesDesc: 'Key admin changes for quick operational traceability.',
+ viewAllActivities: 'View all',
+ activityTimelineTitle: 'Activity Timeline',
+ activityTimelineFiltered: 'Current Results',
+ activityTimelineStoredTotal: 'Retained Records',
+ activityKeyword: 'Keyword',
+ activityKeywordPlaceholder: 'Search target, action, or ID',
+ activityActionFilter: 'Action',
+ activityAllActions: 'All actions',
+ activityTargetTypeFilter: 'Target type',
+ activityAllTargets: 'All targets',
+ activityResetFilters: 'Reset',
+ activityRefresh: 'Refresh',
+ activityApplyFilters: 'Apply',
+ activityLoading: 'Loading activity records...',
+ activityNoFiltered: 'No matching activity records',
+ activityLoadFailed: 'Failed to load activity timeline',
noActivities: 'No activity yet',
activityUnknownTarget: 'Unnamed target',
activityDescription: 'Target: {target}',
@@ -871,6 +910,13 @@ export default {
'local_file.delete': 'Deleted local file',
'local_file.share': 'Shared local file'
},
+ activityTargetTypes: {
+ file: 'File',
+ view_preset: 'View preset',
+ config: 'System settings',
+ local_file: 'Local file',
+ system: 'System'
+ },
recentFiles: 'Recent Shares',
recentFilesDesc: 'Recently created share records for quick status checks.',
available: 'Available',
diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts
index 3864eb5..cee7f8a 100644
--- a/src/i18n/locales/zh-CN.ts
+++ b/src/i18n/locales/zh-CN.ts
@@ -90,6 +90,22 @@ export default {
textType: '文本',
recentActivities: '最近操作',
recentActivitiesDesc: '记录管理员最近的关键改动,便于回溯处理链路。',
+ viewAllActivities: '查看全部',
+ activityTimelineTitle: '操作时间线',
+ activityTimelineFiltered: '当前结果',
+ activityTimelineStoredTotal: '保留记录',
+ activityKeyword: '关键词',
+ activityKeywordPlaceholder: '搜索对象、动作或 ID',
+ activityActionFilter: '操作类型',
+ activityAllActions: '全部操作',
+ activityTargetTypeFilter: '对象类型',
+ activityAllTargets: '全部对象',
+ activityResetFilters: '重置',
+ activityRefresh: '刷新',
+ activityApplyFilters: '筛选',
+ activityLoading: '正在加载操作记录...',
+ activityNoFiltered: '没有匹配的操作记录',
+ activityLoadFailed: '操作时间线加载失败',
noActivities: '暂无操作记录',
activityUnknownTarget: '未命名对象',
activityDescription: '对象:{target}',
@@ -109,6 +125,13 @@ export default {
'local_file.delete': '删除本地文件',
'local_file.share': '分享本地文件'
},
+ activityTargetTypes: {
+ file: '文件',
+ view_preset: '视图预设',
+ config: '系统配置',
+ local_file: '本地文件',
+ system: '系统'
+ },
recentFiles: '最近分享',
recentFilesDesc: '最近创建的分享记录,便于快速核对状态。',
available: '可取件',
@@ -814,6 +837,22 @@ export default {
textType: '文本',
recentActivities: '最近操作',
recentActivitiesDesc: '记录管理员最近的关键改动,便于回溯处理链路。',
+ viewAllActivities: '查看全部',
+ activityTimelineTitle: '操作时间线',
+ activityTimelineFiltered: '当前结果',
+ activityTimelineStoredTotal: '保留记录',
+ activityKeyword: '关键词',
+ activityKeywordPlaceholder: '搜索对象、动作或 ID',
+ activityActionFilter: '操作类型',
+ activityAllActions: '全部操作',
+ activityTargetTypeFilter: '对象类型',
+ activityAllTargets: '全部对象',
+ activityResetFilters: '重置',
+ activityRefresh: '刷新',
+ activityApplyFilters: '筛选',
+ activityLoading: '正在加载操作记录...',
+ activityNoFiltered: '没有匹配的操作记录',
+ activityLoadFailed: '操作时间线加载失败',
noActivities: '暂无操作记录',
activityUnknownTarget: '未命名对象',
activityDescription: '对象:{target}',
@@ -833,6 +872,13 @@ export default {
'local_file.delete': '删除本地文件',
'local_file.share': '分享本地文件'
},
+ activityTargetTypes: {
+ file: '文件',
+ view_preset: '视图预设',
+ config: '系统配置',
+ local_file: '本地文件',
+ system: '系统'
+ },
recentFiles: '最近分享',
recentFilesDesc: '最近创建的分享记录,便于快速核对状态。',
available: '可取件',
diff --git a/src/services/stats.ts b/src/services/stats.ts
index c275a12..0eabfd6 100644
--- a/src/services/stats.ts
+++ b/src/services/stats.ts
@@ -1,8 +1,19 @@
import api from './client'
-import type { ApiResponse, DashboardData } from '@/types'
+import type {
+ ApiResponse,
+ DashboardActivitiesParams,
+ DashboardActivitiesResponse,
+ DashboardData
+} from '@/types'
export class StatsService {
static async getDashboard(): Promise> {
return api.get('/admin/dashboard')
}
+
+ static async getActivities(
+ params: DashboardActivitiesParams
+ ): Promise> {
+ return api.get('/admin/activities', { params })
+ }
}
diff --git a/src/types/dashboard.ts b/src/types/dashboard.ts
index b308a83..e472ab3 100644
--- a/src/types/dashboard.ts
+++ b/src/types/dashboard.ts
@@ -85,6 +85,41 @@ export interface DashboardActivityViewItem extends DashboardActivity {
createdAtValue: string | null
}
+export interface DashboardActivityOption {
+ value: string
+ label?: string
+ count: number
+}
+
+export interface DashboardActivityFilters {
+ action?: string
+ targetType?: string
+ target_type?: string
+ keyword?: string
+}
+
+export interface DashboardActivitiesParams {
+ limit?: number
+ action?: string
+ targetType?: string
+ target_type?: string
+ keyword?: string
+}
+
+export interface DashboardActivitiesResponse {
+ activities?: DashboardActivity[]
+ items?: DashboardActivity[]
+ total?: number
+ storedTotal?: number
+ stored_total?: number
+ limit?: number
+ filters?: DashboardActivityFilters
+ actionOptions?: DashboardActivityOption[]
+ action_options?: DashboardActivityOption[]
+ targetTypeOptions?: DashboardActivityOption[]
+ target_type_options?: DashboardActivityOption[]
+}
+
export type DashboardViewData = Omit<
DashboardData,
| keyof DashboardHealthSummary
diff --git a/src/views/manage/DashboardView.vue b/src/views/manage/DashboardView.vue
index 7a5cc0d..12842d7 100644
--- a/src/views/manage/DashboardView.vue
+++ b/src/views/manage/DashboardView.vue
@@ -265,10 +265,19 @@
{{ t('admin.dashboard.recentActivitiesDesc') }}
-
+
+
+
+
+
+
+
+
+ {{ t('admin.dashboard.activityTimelineFiltered') }}
+
+
+ {{ activityTimelineTotal }}
+
+
+
+
+ {{ t('admin.dashboard.activityTimelineStoredTotal') }}
+
+
+ {{ activityTimelineStoredTotal }}
+
+
+
+
+
+
+
+
+
+