📈 修复趋势坐标轴与移动日期选择

This commit is contained in:
2026-07-10 11:40:11 +08:00
parent 398aad011f
commit dad3b97dfc
3 changed files with 54 additions and 15 deletions
@@ -212,6 +212,32 @@ const updatePart = (part: DatePart, value: string) => {
pointer-events: none; pointer-events: none;
} }
@media (max-width: 639px) {
.native-date-strip {
min-height: 2.35rem;
padding-inline: 0.35ch;
}
.native-date-part {
height: 2.25rem;
font-size: 0.82rem;
}
.native-date-year {
width: 3.7ch;
}
.native-date-month,
.native-date-day {
width: 1.8ch;
}
.native-date-separator {
margin-inline: 0.15ch;
font-size: 0.82rem;
}
}
.dark .native-date-strip { .dark .native-date-strip {
background: rgba(38, 57, 65, 0.62); background: rgba(38, 57, 65, 0.62);
color: #d8e3e5; color: #d8e3e5;
+24 -9
View File
@@ -44,10 +44,11 @@ const emit = defineEmits<{
}>() }>()
type DragState = { type DragState = {
startX: number startOffset: number
lastX: number lastX: number
time: number time: number
pointerType: string pointerType: string
isTimeline: boolean
moved: boolean moved: boolean
} }
@@ -162,10 +163,11 @@ const draw = (timestamp = performance.now()) => {
const padBottom = mobile ? 42 : 36 const padBottom = mobile ? 42 : 36
const width = Math.max(1, rect.width - padLeft - padRight) const width = Math.max(1, rect.width - padLeft - padRight)
const height = Math.max(1, rect.height - padTop - padBottom) const height = Math.max(1, rect.height - padTop - padBottom)
const max = Math.max( const rawMax = Math.max(
1, 0,
...visible.flatMap((row) => [Number(row.downloads || 0), Number(row.uploads || 0)]) ...visible.flatMap((row) => [Number(row.downloads || 0), Number(row.uploads || 0)])
) )
const max = rawMax <= 2 ? 3 : Math.ceil(rawMax / 3) * 3
const muted = props.isDarkMode ? '#9ca3af' : '#6b7280' const muted = props.isDarkMode ? '#9ca3af' : '#6b7280'
const border = props.isDarkMode ? '#374151' : '#e5e7eb' const border = props.isDarkMode ? '#374151' : '#e5e7eb'
const downloadColor = props.isDarkMode ? '#8fb6c5' : '#5f8fa3' const downloadColor = props.isDarkMode ? '#8fb6c5' : '#5f8fa3'
@@ -185,17 +187,23 @@ const draw = (timestamp = performance.now()) => {
} }
const denominator = Math.max(1, windowSize.value - 1) const denominator = Math.max(1, windowSize.value - 1)
const plotLeft = padLeft
const plotRight = padLeft + width
const buildPoints = (field: 'downloads' | 'uploads') => const buildPoints = (field: 'downloads' | 'uploads') =>
visible visible
.map((row, index) => { .map((row, index) => {
const sourceIndex = start + index const sourceIndex = start + index
return { return {
x: padLeft + ((sourceIndex - offset.value) / denominator) * width, x: clamp(
padLeft + ((sourceIndex - offset.value) / denominator) * width,
plotLeft,
plotRight
),
y: padTop + height - (Number(row[field] || 0) / max) * height, y: padTop + height - (Number(row[field] || 0) / max) * height,
row row
} }
}) })
.filter((point) => point.x >= padLeft - 50 && point.x <= padLeft + width + 50) .filter((point) => point.x >= plotLeft && point.x <= plotRight)
const drawSeries = (points: ChartPoint[], color: string) => { const drawSeries = (points: ChartPoint[], color: string) => {
ctx.strokeStyle = color ctx.strokeStyle = color
@@ -392,17 +400,21 @@ const setHoverFromClientX = (clientX: number) => {
} }
const handlePointerDown = (event: PointerEvent) => { const handlePointerDown = (event: PointerEvent) => {
const rect = canvasRef.value?.getBoundingClientRect()
canvasRef.value?.setPointerCapture(event.pointerId) canvasRef.value?.setPointerCapture(event.pointerId)
cancelInertia() cancelInertia()
velocity.value = 0 velocity.value = 0
setHoverFromClientX(event.clientX) setHoverFromClientX(event.clientX)
const isTimeline = Boolean(rect && event.clientY - rect.top >= rect.height - 52)
drag.value = { drag.value = {
startX: event.clientX, startOffset: offset.value,
lastX: event.clientX, lastX: event.clientX,
time: event.timeStamp || performance.now(), time: event.timeStamp || performance.now(),
pointerType: event.pointerType, pointerType: event.pointerType,
isTimeline,
moved: false moved: false
} }
if (isTimeline) canvasRef.value!.style.touchAction = 'none'
} }
const handlePointerMove = (event: PointerEvent) => { const handlePointerMove = (event: PointerEvent) => {
@@ -416,12 +428,12 @@ const handlePointerMove = (event: PointerEvent) => {
return return
} }
if (drag.value.pointerType === 'touch') { if (drag.value.pointerType === 'touch' && !drag.value.isTimeline) {
scheduleDraw() scheduleDraw()
return return
} }
const totalDistance = Math.abs(latestEvent.clientX - drag.value.startX) const totalDistance = Math.abs(latestEvent.clientX - drag.value.lastX)
const threshold = 3 const threshold = 3
if (!drag.value.moved && totalDistance < threshold) { if (!drag.value.moved && totalDistance < threshold) {
scheduleDraw() scheduleDraw()
@@ -440,15 +452,18 @@ const handlePointerMove = (event: PointerEvent) => {
} }
const handlePointerUp = () => { const handlePointerUp = () => {
const timelineDrag = drag.value?.isTimeline
const shouldInertia = Boolean(drag.value?.moved) const shouldInertia = Boolean(drag.value?.moved)
drag.value = null drag.value = null
if (canvasRef.value) canvasRef.value.style.touchAction = ''
cancelInertia() cancelInertia()
if (shouldInertia) inertiaRafId.value = window.requestAnimationFrame(applyInertia) if (shouldInertia && !timelineDrag) inertiaRafId.value = window.requestAnimationFrame(applyInertia)
else scheduleDraw() else scheduleDraw()
} }
const handlePointerCancel = () => { const handlePointerCancel = () => {
drag.value = null drag.value = null
if (canvasRef.value) canvasRef.value.style.touchAction = ''
} }
const handlePointerLeave = () => { const handlePointerLeave = () => {
+4 -6
View File
@@ -239,10 +239,9 @@
<p class="text-sm" :class="[mutedTextClass]"> <p class="text-sm" :class="[mutedTextClass]">
{{ analyticsSummaryText }} {{ analyticsSummaryText }}
</p> </p>
<p class="mt-1 text-xs sm:hidden" :class="[mutedTextClass]">轻触或滑动查看数据使用下方按钮调整范围</p> <p class="mt-1 text-xs sm:hidden" :class="[mutedTextClass]">轻触或滑动查看数据拖动底部日期轴平移图表</p>
</div> </div>
<div class="w-full max-w-full space-y-3 lg:w-auto"> <div class="flex w-full min-w-0 items-center justify-center gap-1.5 sm:w-auto sm:flex-row sm:gap-2">
<div class="flex flex-col gap-2 sm:flex-row sm:items-center">
<NativeDateSelect <NativeDateSelect
v-model="analyticsStart" v-model="analyticsStart"
:field-class="fieldClass" :field-class="fieldClass"
@@ -250,7 +249,7 @@
@change="fetchAnalyticsData" @change="fetchAnalyticsData"
/> />
<span <span
class="self-center text-sm font-semibold" class="shrink-0 text-sm font-semibold"
:class="[isDarkMode ? 'text-[#aabdc2]' : 'text-[#789096]']" :class="[isDarkMode ? 'text-[#aabdc2]' : 'text-[#789096]']"
> >
- -
@@ -262,7 +261,7 @@
@change="fetchAnalyticsData" @change="fetchAnalyticsData"
/> />
</div> </div>
<div class="grid grid-cols-3 gap-2 sm:flex sm:justify-end"> <div class="hidden grid-cols-3 gap-2 sm:flex sm:justify-end">
<BaseButton <BaseButton
variant="outline" variant="outline"
size="sm" size="sm"
@@ -288,7 +287,6 @@
重置 重置
</BaseButton> </BaseButton>
</div> </div>
</div>
</div> </div>
<div <div
v-if="analyticsError" v-if="analyticsError"