2026-04-30 11:04:48 +08:00
|
|
|
<script setup>
|
|
|
|
|
import { computed, ref } from 'vue'
|
|
|
|
|
import { caseCategories, cases } from '../data/homepage'
|
|
|
|
|
import SectionTitle from './SectionTitle.vue'
|
|
|
|
|
|
|
|
|
|
const active = ref(caseCategories[0])
|
|
|
|
|
const filteredCases = computed(() => cases.filter(item => item.category === active.value))
|
|
|
|
|
|
|
|
|
|
const selectCategory = (category) => {
|
|
|
|
|
active.value = category
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleCategoryKeydown = (event, index) => {
|
|
|
|
|
const keyMap = {
|
|
|
|
|
ArrowRight: 1,
|
|
|
|
|
ArrowDown: 1,
|
|
|
|
|
ArrowLeft: -1,
|
|
|
|
|
ArrowUp: -1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event.key === 'Home' || event.key === 'End' || keyMap[event.key]) {
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
} else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const nextIndex =
|
|
|
|
|
event.key === 'Home'
|
|
|
|
|
? 0
|
|
|
|
|
: event.key === 'End'
|
|
|
|
|
? caseCategories.length - 1
|
|
|
|
|
: (index + keyMap[event.key] + caseCategories.length) % caseCategories.length
|
|
|
|
|
|
|
|
|
|
selectCategory(caseCategories[nextIndex])
|
|
|
|
|
document.getElementById(`case-filter-${nextIndex}`)?.focus()
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<section id="cases" class="case-section page-section page-section--compact">
|
|
|
|
|
<div class="container">
|
|
|
|
|
<SectionTitle title="客户案例" />
|
|
|
|
|
<div class="case-layout">
|
|
|
|
|
<aside class="case-filter" role="tablist" aria-label="客户案例分类">
|
|
|
|
|
<button
|
|
|
|
|
v-for="(category, index) in caseCategories"
|
|
|
|
|
:key="category"
|
|
|
|
|
:id="`case-filter-${index}`"
|
|
|
|
|
type="button"
|
|
|
|
|
:class="['case-filter-btn', { active: category === active }]"
|
|
|
|
|
role="tab"
|
|
|
|
|
:aria-selected="category === active"
|
|
|
|
|
:aria-controls="'case-panel'"
|
|
|
|
|
:tabindex="category === active ? 0 : -1"
|
|
|
|
|
@click="selectCategory(category)"
|
|
|
|
|
@keydown="handleCategoryKeydown($event, index)"
|
|
|
|
|
>
|
|
|
|
|
{{ category }}
|
|
|
|
|
</button>
|
|
|
|
|
</aside>
|
|
|
|
|
<div id="case-panel" class="case-grid" role="tabpanel" :aria-label="active">
|
|
|
|
|
<article v-for="item in filteredCases" :key="`${active}-${item.name}`" class="case-card">
|
2026-04-30 13:34:02 +08:00
|
|
|
<div :class="['case-logo-slot', { 'case-logo-slot--placeholder': !item.logo }]">
|
|
|
|
|
<img v-if="item.logo" :src="item.logo" :alt="`${item.name.replace(/\n/g, '')} logo`" />
|
|
|
|
|
<span v-else :class="['case-logo-fallback', `case-logo--${item.tone}`]">{{ item.short }}</span>
|
|
|
|
|
</div>
|
2026-04-30 11:04:48 +08:00
|
|
|
<p>{{ item.name }}</p>
|
|
|
|
|
</article>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</template>
|