master
lion 2 days ago
parent b205b9949d
commit cb7a4a6147

@ -199,6 +199,40 @@ function parseVenuePcRowsFromResponse(p: HikPeopleCountingResponse | null): Dash
const dashboardVenuePcRows = computed((): DashboardVenuePcRow[] => parseVenuePcRowsFromResponse(pcData.value))
type VenuePcViewMode = 'chart' | 'table'
const venuePcViewMode = ref<VenuePcViewMode>('chart')
const dashboardVenuePcEnterTotal = computed(() =>
dashboardVenuePcRows.value.reduce((sum, row) => sum + (Number(row.enter) || 0), 0),
)
const venuePcRangeLabel = computed(() => {
const picked = pickVenuePcRangeFromPicker()
if (!picked) return ''
return picked.start === picked.end ? picked.start : `${picked.start}${picked.end}`
})
const venuePcMaxEnter = computed(() => {
const rows = dashboardVenuePcRows.value
if (!rows.length) return 1
return Math.max(1, ...rows.map((r) => Number(r.enter) || 0))
})
function venuePcBarHeightPct(enter: number): string {
const max = venuePcMaxEnter.value
const v = Number(enter) || 0
if (v <= 0) return '0%'
return `${Math.max(6, (v / max) * 100)}%`
}
function onVenuePcChartWheel(e: WheelEvent) {
const el = e.currentTarget as HTMLElement | null
if (!el || el.scrollWidth <= el.clientWidth + 1) return
if (Math.abs(e.deltaX) >= Math.abs(e.deltaY)) return
e.preventDefault()
el.scrollLeft += e.deltaY
}
function dashboardVenuePcSummary(ctx: { data: DashboardVenuePcRow[] }) {
const rows = Array.isArray(ctx.data) ? ctx.data : []
let enter = 0
@ -1075,18 +1109,75 @@ onMounted(async () => {
<a-spin :loading="pcLoading">
<a-alert v-if="pcError" type="warning" show-icon style="margin-bottom: 10px">{{ pcError }}</a-alert>
<div v-if="dashboardVenuePcRows.length" class="dash-venue-pc-table-wrap">
<a-table
class="dash-table dash-venue-pc-table"
row-key="venueId"
:columns="dashboardVenuePcColumns"
:data="dashboardVenuePcRows"
:pagination="false"
size="small"
table-layout-fixed
:scroll="{ y: 260 }"
:summary="dashboardVenuePcSummary"
/>
<div v-if="dashboardVenuePcRows.length" class="dash-venue-pc-content">
<div class="dash-venue-pc-viewbar">
<div class="dash-venue-pc-total">
<span v-if="venuePcRangeLabel" class="dash-venue-pc-total__range">{{ venuePcRangeLabel }}</span>
<span class="dash-venue-pc-total__label">合计入馆人数</span>
<strong class="dash-venue-pc-total__value">{{ dashboardVenuePcEnterTotal }}</strong>
</div>
<a-space size="small">
<a-button
size="small"
:type="venuePcViewMode === 'chart' ? 'primary' : 'secondary'"
@click="venuePcViewMode = 'chart'"
>
图表
</a-button>
<a-button
size="small"
:type="venuePcViewMode === 'table' ? 'primary' : 'secondary'"
@click="venuePcViewMode = 'table'"
>
数据
</a-button>
</a-space>
</div>
<div v-show="venuePcViewMode === 'chart'" class="dash-venue-pc-chart">
<div
class="dash-venue-pc-chart__scroll"
role="img"
aria-label="各场馆入馆人数柱状图"
@wheel="onVenuePcChartWheel"
>
<div class="dash-venue-pc-chart__plot">
<div
v-for="row in dashboardVenuePcRows"
:key="String(row.venueId)"
class="dash-venue-pc-chart__col"
>
<span class="dash-venue-pc-chart__col-value">{{ row.enter }}</span>
<div class="dash-venue-pc-chart__col-track">
<div
class="dash-venue-pc-chart__col-bar"
:style="{ height: venuePcBarHeightPct(row.enter) }"
/>
</div>
<span
class="dash-venue-pc-chart__col-label"
:title="String(row.venueName || row.venueId)"
>
{{ row.venueName || row.venueId }}
</span>
</div>
</div>
</div>
</div>
<div v-show="venuePcViewMode === 'table'" class="dash-venue-pc-table-wrap">
<a-table
class="dash-table dash-venue-pc-table"
row-key="venueId"
:columns="dashboardVenuePcColumns"
:data="dashboardVenuePcRows"
:pagination="false"
size="small"
table-layout-fixed
:scroll="{ y: 260 }"
:summary="dashboardVenuePcSummary"
/>
</div>
</div>
<a-empty v-else-if="!pcLoading && !pcError" description="暂无数据,可调时间段或检查客流归档与场馆映射" />
</a-spin>
@ -1334,6 +1425,8 @@ onMounted(async () => {
border: 1px solid #e8eaed;
border-radius: 12px;
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.04);
min-width: 0;
max-width: 100%;
}
.dash-bundle .dash-overview-dual,
@ -2031,6 +2124,7 @@ onMounted(async () => {
.dash-metric-card--venue-pc {
width: 100%;
min-width: 0;
}
.dash-metric-card__icon--venue-pc {
@ -2040,12 +2134,156 @@ onMounted(async () => {
.dash-metric-card__body--venue-pc {
min-height: 0;
min-width: 0;
}
.dash-metric-card__body--venue-pc :deep(.arco-spin),
.dash-metric-card__body--venue-pc :deep(.arco-spin-children) {
display: block;
width: 100%;
max-width: 100%;
min-width: 0;
}
.dash-venue-pc-table-wrap {
width: 100%;
}
.dash-venue-pc-content {
width: 100%;
max-width: 100%;
min-width: 0;
overflow: hidden;
}
.dash-venue-pc-viewbar {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 10px 16px;
margin-bottom: 12px;
}
.dash-venue-pc-total {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 6px 10px;
font-size: 13px;
color: #4e5969;
}
.dash-venue-pc-total__range {
color: #86909c;
font-size: 12px;
}
.dash-venue-pc-total__value {
color: #165dff;
font-size: 20px;
font-weight: 700;
font-variant-numeric: tabular-nums;
line-height: 1.2;
}
.dash-venue-pc-chart {
width: 100%;
max-width: 100%;
min-width: 0;
padding: 4px 0 0;
}
.dash-venue-pc-chart__scroll {
width: 100%;
max-width: 100%;
min-width: 0;
overflow-x: scroll;
overflow-y: hidden;
overscroll-behavior-x: contain;
-webkit-overflow-scrolling: touch;
touch-action: pan-x;
padding-bottom: 6px;
border-radius: 8px;
background: #fafbfd;
border: 1px solid #f0f1f5;
}
.dash-venue-pc-chart__scroll::-webkit-scrollbar {
height: 8px;
}
.dash-venue-pc-chart__scroll::-webkit-scrollbar-thumb {
background: #c9cdd4;
border-radius: 4px;
}
.dash-venue-pc-chart__scroll::-webkit-scrollbar-track {
background: #f2f3f5;
border-radius: 4px;
}
.dash-venue-pc-chart__plot {
display: inline-flex;
flex-wrap: nowrap;
align-items: flex-end;
gap: 6px;
min-height: 280px;
padding: 8px 8px 4px;
box-sizing: border-box;
vertical-align: top;
}
.dash-venue-pc-chart__col {
flex: 0 0 38px;
width: 38px;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.dash-venue-pc-chart__col-value {
font-size: 11px;
font-weight: 600;
color: #1d2129;
font-variant-numeric: tabular-nums;
line-height: 1.2;
white-space: nowrap;
}
.dash-venue-pc-chart__col-track {
width: 28px;
height: 220px;
display: flex;
align-items: flex-end;
justify-content: center;
border-radius: 4px 4px 0 0;
background: linear-gradient(180deg, #f7f8fa 0%, #fafbfd 100%);
}
.dash-venue-pc-chart__col-bar {
width: 20px;
min-height: 0;
border-radius: 4px 4px 0 0;
background: linear-gradient(180deg, #4080ff 0%, #165dff 100%);
transition: height 0.25s ease;
}
.dash-venue-pc-chart__col-label {
width: 38px;
font-size: 10px;
line-height: 1.3;
color: #4e5969;
text-align: center;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
word-break: break-all;
}
.dash-venue-pc-bundle :deep(.dash-venue-pc-table.arco-table),
.dash-venue-pc-table-wrap :deep(.dash-venue-pc-table.arco-table) {
width: 100%;

Loading…
Cancel
Save