From 520152477f13d87a50cba88ae1ff7e76a8b60266 Mon Sep 17 00:00:00 2001 From: lion <120344285@qq.com> Date: Mon, 22 Jun 2026 14:38:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/Dashboard.vue | 139 ++++++++++++++++++++++++++++++++++------ 1 file changed, 119 insertions(+), 20 deletions(-) diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index 004b1a4..7b15acc 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -55,6 +55,9 @@ const stats = ref({ role: '', venue_id: undefined as number | undefined, activity_id: undefined as number | undefined, + venue_ids: [] as number[], + people_counting_venue_ids: [] as number[], + show_venue_people_stats: false, }, summary: { activity_sessions: 0, @@ -197,7 +200,30 @@ function parseVenuePcRowsFromResponse(p: HikPeopleCountingResponse | null): Dash }) } -const dashboardVenuePcRows = computed((): DashboardVenuePcRow[] => parseVenuePcRowsFromResponse(pcData.value)) +const isVenueAdminOnlyDashboard = computed(() => isVenueAdmin.value && !isSuperAdmin.value) + +function filterVenuePcRowsForScope(rows: DashboardVenuePcRow[]): DashboardVenuePcRow[] { + if (!isVenueAdminOnlyDashboard.value) return rows + const scopedIds = stats.value.scope.people_counting_venue_ids + if (!scopedIds.length) return [] + const allowed = new Set(scopedIds.map((id) => String(id))) + return rows.filter((row) => allowed.has(String(row.venueId))) +} + +const showVenuePeopleStatsBlock = computed(() => { + if (isVenueAdminOnlyDashboard.value) { + return stats.value.scope.show_venue_people_stats === true && peopleCountingConfigured.value + } + return true +}) + +const venuePeopleStatsTitle = computed(() => + isVenueAdminOnlyDashboard.value ? '场馆人数统计' : '各场馆人数统计', +) + +const dashboardVenuePcRows = computed((): DashboardVenuePcRow[] => + filterVenuePcRowsForScope(parseVenuePcRowsFromResponse(pcData.value)), +) type VenuePcViewMode = 'chart' | 'table' const venuePcViewMode = ref('chart') @@ -414,7 +440,10 @@ type VenuePcExportPreset = { sheetName: string } -function buildVenuePcExportPreset(kind: 'today' | 'week' | 'month' | 'year'): VenuePcExportPreset { +function buildVenuePcExportPreset( + kind: 'today' | 'week' | 'month' | 'year', + statsLabel = '各场馆人数统计', +): VenuePcExportPreset { const today = todayCalendar() const todayStr = ymdCalendar(today) if (kind === 'today') { @@ -422,7 +451,7 @@ function buildVenuePcExportPreset(kind: 'today' | 'week' | 'month' | 'year'): Ve start: todayStr, end: todayStr, periodLabel: todayStr, - sheetName: `各场馆人数统计-当天(${todayStr})`, + sheetName: `${statsLabel}-当天(${todayStr})`, } } if (kind === 'week') { @@ -433,7 +462,7 @@ function buildVenuePcExportPreset(kind: 'today' | 'week' | 'month' | 'year'): Ve start, end, periodLabel: `${start}至${end}`, - sheetName: `各场馆人数统计-本周(${start}至${end})`, + sheetName: `${statsLabel}-本周(${start}至${end})`, } } if (kind === 'month') { @@ -445,7 +474,7 @@ function buildVenuePcExportPreset(kind: 'today' | 'week' | 'month' | 'year'): Ve start, end, periodLabel: `${start}至${end}`, - sheetName: `各场馆人数统计-本月(${start}至${end})`, + sheetName: `${statsLabel}-本月(${start}至${end})`, } } const first = new Date(today.getFullYear(), 0, 1) @@ -456,7 +485,7 @@ function buildVenuePcExportPreset(kind: 'today' | 'week' | 'month' | 'year'): Ve start, end, periodLabel: `${start}至${end}`, - sheetName: `各场馆人数统计-本年(${start}至${end})`, + sheetName: `${statsLabel}-本年(${start}至${end})`, } } @@ -467,7 +496,63 @@ function dashboardTicketGrabVerifyRatePctStr(): string { return `${p != null ? p : 0}%` } +async function exportVenueAdminDashboardExcel() { + if (exportingDashboard.value) return + exportingDashboard.value = true + try { + const wb = XLSX.utils.book_new() + const act = stats.value.activity_schedule_counts ?? ({} as ActivityScheduleCountsBlock) + + appendDashboardWorkbookSheet( + wb, + buildMetricSheet([ + ['活动数', act.total ?? 0], + ['总场次', act.total_sessions ?? 0], + ['未开始', act.not_started ?? 0], + ['进行中', act.ongoing ?? 0], + ['已结束', act.ended ?? 0], + ]), + '活动统计', + ) + + const statsLabel = venuePeopleStatsTitle.value + if (stats.value.scope.show_venue_people_stats && getPeopleCountingEndpoint()) { + const pcPresets: VenuePcExportPreset[] = (['today', 'week', 'month', 'year'] as const).map((k) => + buildVenuePcExportPreset(k, statsLabel), + ) + const pcResponses = await Promise.all( + pcPresets.map((preset) => fetchHikPeopleCounting(preset.start, preset.end).catch(() => null)), + ) + pcPresets.forEach((preset, idx) => { + const resp = pcResponses[idx] + const rows = filterVenuePcRowsForScope(parseVenuePcRowsFromResponse(resp)) + const note = + resp && resp.code !== 200 ? resp.message || `接口返回错误码 ${resp.code}` : undefined + appendDashboardWorkbookSheet( + wb, + buildVenuePcExportSheet(preset.periodLabel, rows, note), + preset.sheetName, + ) + }) + } + + XLSX.writeFile(wb, dashboardExportFilename()) + Message.success('数据看板已导出') + } catch (err: unknown) { + console.error('[工作台] 场馆管理员导出失败', err) + const msg = + err instanceof Error ? err.message : typeof err === 'string' ? err : '导出失败' + Message.error(msg.includes('already exists') ? '导出失败:Sheet 名称重复,请刷新后重试' : msg || '导出失败') + } finally { + exportingDashboard.value = false + } +} + async function exportDashboardExcel() { + if (isVenueAdminOnlyDashboard.value) { + await exportVenueAdminDashboardExcel() + return + } if (exportingDashboard.value) return exportingDashboard.value = true try { @@ -563,7 +648,7 @@ async function exportDashboardExcel() { ) pcPresets.forEach((preset, idx) => { const resp = pcResponses[idx] - const rows = parseVenuePcRowsFromResponse(resp) + const rows = filterVenuePcRowsForScope(parseVenuePcRowsFromResponse(resp)) const note = resp && resp.code !== 200 ? resp.message || `接口返回错误码 ${resp.code}` : undefined appendDashboardWorkbookSheet( @@ -603,13 +688,14 @@ function exportVenuePeopleExcel() { } exportingVenuePc.value = true try { + const statsLabel = venuePeopleStatsTitle.value const table = [['场馆ID', '场馆名称', '入馆总人数'], ...rows.map((r) => [r.venueId, r.venueName, r.enter])] const enterSum = rows.reduce((s, r) => s + (Number(r.enter) || 0), 0) table.push(['合计', '—', enterSum]) const ws = XLSX.utils.aoa_to_sheet(table) const wb = XLSX.utils.book_new() - XLSX.utils.book_append_sheet(wb, ws, '各场馆人数统计') - const filename = `${picked.start}至${picked.end}各场馆人数统计.xlsx` + XLSX.utils.book_append_sheet(wb, ws, sanitizeExcelSheetName(statsLabel)) + const filename = `${picked.start}至${picked.end}${statsLabel}.xlsx` XLSX.writeFile(wb, filename) Message.success('已导出') } finally { @@ -629,7 +715,7 @@ const hasPendingTodoRows = computed( () => pendingActivityItems.value.length > 0 || pendingVenueItems.value.length > 0, ) -const dashOverviewSplit = computed(() => isSuperAdmin.value || isVenueAdmin.value) +const dashOverviewSplit = computed(() => isSuperAdmin.value) const todoEmptyPlaceholder = computed(() => isVenueAdmin.value ? '暂无已退回活动' : '暂无待审核事项', @@ -809,7 +895,10 @@ async function exportDailyVerifyExcel() { onMounted(async () => { await loadMe() await loadStats() - if (getPeopleCountingEndpoint()) { + const shouldLoadVenuePeople = + getPeopleCountingEndpoint() && + (!isVenueAdminOnlyDashboard.value || stats.value.scope.show_venue_people_stats === true) + if (shouldLoadVenuePeople) { applyVenuePcRangeToday() await loadVenuePeopleRange({ silentSuccess: true }) } @@ -830,14 +919,20 @@ onMounted(async () => {

工作台

数据看板 - + 导出数据
-
+
@@ -879,7 +974,7 @@ onMounted(async () => {
-
+
-
+
-
+
{{ stats.activity_schedule_counts.published_venues_count ?? 0 }}
已发布活动场馆
@@ -966,7 +1061,7 @@ onMounted(async () => {
-
+
-
+
-
+
-

各场馆人数统计

+

{{ venuePeopleStatsTitle }}

@@ -1543,6 +1638,10 @@ onMounted(async () => { grid-template-columns: 1fr 1fr; align-items: stretch; } + + .dash-schedule-dual--single { + grid-template-columns: 1fr; + } } .dash-metric-card__icon--schedule-tg {