2026-04-30 11:04:48 +08:00
|
|
|
<script setup>
|
|
|
|
|
import { computed, ref } from 'vue'
|
|
|
|
|
import { caseCategories, cases } from '../data/homepage'
|
2026-05-09 15:38:24 +08:00
|
|
|
import LucideHospitalIcon from './LucideHospitalIcon.vue'
|
2026-04-30 11:04:48 +08:00
|
|
|
import SectionTitle from './SectionTitle.vue'
|
|
|
|
|
|
|
|
|
|
const active = ref(caseCategories[0])
|
|
|
|
|
const filteredCases = computed(() => cases.filter(item => item.category === active.value))
|
2026-05-06 16:09:19 +08:00
|
|
|
const activeIndex = computed(() => caseCategories.findIndex(category => category === active.value))
|
2026-05-06 16:27:46 +08:00
|
|
|
const categoryLineMap = {
|
|
|
|
|
国家医学中心: ['国家医学', '中心'],
|
|
|
|
|
公立医院: ['公立医院'],
|
|
|
|
|
妇幼保健客户: ['妇幼保健', '客户'],
|
|
|
|
|
医疗集团客户: ['医疗集团', '客户']
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getCategoryLines = (category) => categoryLineMap[category] ?? [category]
|
2026-04-30 11:04:48 +08:00
|
|
|
|
|
|
|
|
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)"
|
|
|
|
|
>
|
2026-05-06 16:27:46 +08:00
|
|
|
<span class="case-filter-text">
|
|
|
|
|
<span v-for="line in getCategoryLines(category)" :key="line">{{ line }}</span>
|
|
|
|
|
</span>
|
2026-04-30 11:04:48 +08:00
|
|
|
</button>
|
2026-05-06 16:09:19 +08:00
|
|
|
<span class="case-tab-indicator" :style="{ transform: `translateX(${activeIndex * 100}%)` }"></span>
|
2026-04-30 11:04:48 +08:00
|
|
|
</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 16:08:32 +08:00
|
|
|
<div :class="['case-logo-slot', item.logoFit && `case-logo-slot--${item.logoFit}`, { 'case-logo-slot--placeholder': !item.logo }]">
|
2026-04-30 13:34:02 +08:00
|
|
|
<img v-if="item.logo" :src="item.logo" :alt="`${item.name.replace(/\n/g, '')} logo`" />
|
2026-05-09 15:38:24 +08:00
|
|
|
<span v-else class="case-logo-fallback">
|
|
|
|
|
<LucideHospitalIcon :size="30" />
|
|
|
|
|
</span>
|
2026-04-30 13:34:02 +08:00
|
|
|
</div>
|
2026-04-30 11:04:48 +08:00
|
|
|
<p>{{ item.name }}</p>
|
|
|
|
|
</article>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</template>
|