Files
Alfred Tang a79ff81be0 fix: resolve chat messages not loading due to Nuxt auto-import bug and WCDB timeout
- Move frontend/utils/ to frontend/lib/ to avoid Nuxt unimport scanner
  incorrectly extracting function parameter names (value, fallback) as
  module-level exports, which injected phantom imports that broke all
  client-side JavaScript execution
- Update all import paths across 13 files from ~/utils/ to ~/lib/
- Add timeout (5s) and negative cache (60s TTL) to WCDB realtime
  ensure_connected() to prevent open_account() from hanging indefinitely
  when the WeChat database is locked
- Reorder selectContact() to fire loadMessages() before navigateTo() so
  the message fetch starts before route navigation triggers Suspense
- Add watch: false to SSR useAsyncData calls to prevent unnecessary
  re-fetching on client-side route changes
2026-03-14 17:17:10 +08:00

115 lines
4.4 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="w-full">
<div class="flex items-center justify-between gap-4">
<div class="wrapped-body text-sm text-[#7F7F7F]">
<span class="wrapped-number text-[#07C160] font-semibold">{{ totalMessages }}</span> 条消息
</div>
<div class="wrapped-label text-xs text-[#00000066]">24H x 7Days</div>
</div>
<div class="mt-4 overflow-x-auto" data-wrapped-scroll-x>
<!-- Keep original style, but slightly shrink the overall grid width (thus shrinking cells). -->
<div class="min-w-[520px] max-w-[600px] mx-auto">
<div class="grid gap-[3px] [grid-template-columns:24px_1fr] text-[11px] text-[#00000066] mb-2">
<div></div>
<div class="grid gap-[3px] [grid-template-columns:repeat(24,minmax(0,1fr))]">
<span
v-for="(s, idx) in timeLabels"
:key="idx"
class="col-span-4 wrapped-number"
>
{{ s }}
</span>
</div>
</div>
<div class="grid gap-[3px] [grid-template-columns:24px_1fr] items-stretch">
<div class="grid gap-[3px] [grid-template-rows:repeat(7,minmax(0,1fr))] text-[11px] text-[#00000066]">
<div v-for="(w, wi) in weekdayLabels" :key="wi" class="flex items-center wrapped-body">
{{ w }}
</div>
</div>
<div class="grid gap-[3px] [grid-template-columns:repeat(24,minmax(0,1fr))]">
<template v-for="(row, wi) in matrixSafe" :key="wi">
<div
v-for="(v, hi) in row"
:key="`${wi}-${hi}`"
class="heatmap-cell aspect-square min-h-[10px] rounded-[2px] transition-transform duration-150 hover:scale-125 hover:z-10 relative"
:style="{ backgroundColor: colorFor(v), transformOrigin: originFor(wi, hi) }"
:title="tooltipFor(wi, hi, v)"
/>
</template>
</div>
</div>
</div>
</div>
<div class="mt-4 flex items-center justify-between text-xs text-[#00000066]">
<div class="flex items-center gap-2">
<span class="wrapped-body"></span>
<div class="flex items-center gap-[2px]">
<span v-for="i in 6" :key="i" class="heatmap-legend-cell w-4 h-2 rounded-[2px]" :style="{ backgroundColor: legendColor(i) }"></span>
</div>
<span class="wrapped-body"></span>
</div>
<div v-if="maxValue > 0" class="wrapped-number">最大 {{ maxValue }}</div>
</div>
</div>
</template>
<script setup>
import { heatColor, maxInMatrix, formatHourRange } from '~/lib/wrapped/heatmap'
const props = defineProps({
weekdayLabels: { type: Array, default: () => ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
hourLabels: { type: Array, default: () => Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0')) },
matrix: { type: Array, default: () => [] },
totalMessages: { type: Number, default: 0 }
})
const matrixSafe = computed(() => {
// Expect 7x24, but keep defensive to avoid UI crashes.
const m = Array.isArray(props.matrix) ? props.matrix : []
const out = []
for (let i = 0; i < 7; i++) {
const row = Array.isArray(m[i]) ? m[i] : []
const r = []
for (let h = 0; h < 24; h++) r.push(Number(row[h] || 0))
out.push(r)
}
return out
})
const maxValue = computed(() => maxInMatrix(matrixSafe.value))
const timeLabels = computed(() => {
// Show every 4 hours to reduce clutter, inspired by EchoTrace.
const labels = []
for (let i = 0; i < 24; i += 4) labels.push(props.hourLabels[i] ?? String(i).padStart(2, '0'))
return labels
})
const colorFor = (v) => heatColor(v, maxValue.value)
const tooltipFor = (weekdayIndex, hour, v) => {
const w = props.weekdayLabels?.[weekdayIndex] ?? `${weekdayIndex + 1}`
const hr = formatHourRange(hour)
const n = Number(v) || 0
return `${w} ${hr}${n}`
}
const legendColor = (i) => {
const t = i / 6
return heatColor(Math.max(1, t * (maxValue.value || 1)), maxValue.value || 1)
}
const originFor = (weekdayIndex, hour) => {
// Avoid hover scaling pushing scrollWidth/scrollHeight and showing scrollbars:
// keep the "outer" edges anchored on the first/last row/col.
const x = hour === 0 ? 'left' : (hour === 23 ? 'right' : 'center')
const y = weekdayIndex === 0 ? 'top' : (weekdayIndex === 6 ? 'bottom' : 'center')
return `${x} ${y}`
}
</script>