@@ -1,13 +1,14 @@
< script setup lang = "ts" >
import { Icon } from '@iconify/vue' ;
import type { SectionBlock , SpecGroup } from '~/types/site' ;
const props = defineProps < { section : SectionBlock } > ( ) ;
const { overline , title , subtitle , isEn , isDark , overlineStyle , titleStyle , bodyStyle } = useSectionContent ( props . section ) ;
const { overline , title , subtitle , content , isEn , isDark , overlineStyle , titleStyle , bodyStyle } = useSectionContent ( props . section ) ;
const specGroups = computed < SpecGroup [ ] > ( ( ) => props . section . spec _groups ? ? [ ] ) ;
// 每个分组的展开状态(默认收起,仅显示前 default_visible 项 )
// 每个分组的展开状态(默认收起,仅显示前 default_visible 行 )
const expandedMap = ref < Record < number , boolean > > ( { } ) ;
function isExpanded ( group : SpecGroup ) : boolean {
@@ -18,10 +19,10 @@ function toggle(group: SpecGroup) {
expandedMap . value [ group . id ] = ! expandedMap . value [ group . id ] ;
}
// 分组内可见的参数项:展开时全部,收起时 前 default_visible 项
function visibleItems ( group : SpecGroup ) {
// 固定显示的参数项:始终为 前 default_visible 项(默认 5 行),
// 其余项由下方 Transition 区域渲染,避免展开时重复
function fixedItems ( group : SpecGroup ) {
const items = group . items ? ? [ ] ;
if ( isExpanded ( group ) ) return items ;
return items . slice ( 0 , group . default _visible || 5 ) ;
}
@@ -29,11 +30,44 @@ function visibleItems(group: SpecGroup) {
function needToggle ( group : SpecGroup ) : boolean {
return ( group . items ? . length ? ? 0 ) > ( group . default _visible || 5 ) ;
}
// 展开/收起高度过渡:从 0 动画到内容自然高度
// 注意:钩子必须声明并调用 done 回调(Vue 按形参个数判断是否显式接管完成时机),
// 否则 leave 结束后元素不会被移除,导致再次展开失效
const SPEC _TOGGLE _MS = 300 ;
function onEnter ( el : Element , done : ( ) => void ) {
const html = el as HTMLElement ;
html . style . overflow = 'hidden' ;
html . style . height = '0' ;
// 强制 reflow 后再切到目标高度,确保过渡生效
void html . offsetHeight ;
html . style . transition = ` height ${ SPEC _TOGGLE _MS } ms ease ` ;
html . style . height = ` ${ html . scrollHeight } px ` ;
setTimeout ( done , SPEC _TOGGLE _MS ) ;
}
function onAfterEnter ( el : Element ) {
const html = el as HTMLElement ;
html . style . overflow = '' ;
html . style . height = '' ;
html . style . transition = '' ;
}
function onLeave ( el : Element , done : ( ) => void ) {
const html = el as HTMLElement ;
html . style . overflow = 'hidden' ;
html . style . height = ` ${ html . scrollHeight } px ` ;
void html . offsetHeight ;
html . style . transition = ` height ${ SPEC _TOGGLE _MS } ms ease ` ;
html . style . height = '0' ;
setTimeout ( done , SPEC _TOGGLE _MS ) ;
}
< / script >
< template >
< div class = "section-container py-[var(--sec-py,96px)]" >
<!-- 标题 区 -- >
<!-- 区块标题 : 整个区块顶部居中 -- >
< div class = "text-center max-w-640px mx-auto mb-56px" >
< p v-if = "overline" class="text-14px font-semibold tracking-widest uppercase" :class="isDark ? 'text-primary-300' : 'text-primary'" :style="overlineStyle" >
{{ overline }}
@@ -44,49 +78,84 @@ function needToggle(group: SpecGroup): boolean {
< p v-if = "subtitle" class="mt-16px text-base" :class="isDark ? 'text-gray-400' : 'text-gray-500 dark:text-gray-400'" :style="bodyStyle" >
{{ subtitle }}
< / p >
< div v-if = "content" class="rich-text mt-20px text-15px leading-relaxed" :class="isDark ? 'text-gray-400' : 'text-gray-500 dark:text-gray-400'" :style="bodyStyle" v-html="content" / >
< / div >
< ! - - 参数分组卡片 - - >
< div class = "flex flex-col gap-24px max-w-960px mx-auto " >
<!-- 参数分组 : 左分组标题 + 右参数卡片 -- >
< div class = "flex flex-col gap-64px " >
< div
v-for = "group in specGroups"
:key = "group.id"
class = "rounded-16px overflow-hidden "
: class = "isDark ? 'bg-white/5 border border-white/10' : 'bg-white border border-gray-100 shadow-sm dark:bg-dark-800 dark:border-dark-700'"
class = "md:grid md:grid-cols-[minmax(0,5fr)_minmax(0,7fr)] gap-64px items-start "
>
<!-- 分组标题 -- >
< div class = "px-32px py-20px border-b" : class = "isDark ? 'border-white/10' : 'border-gray-100 dark:border-dark-700' ">
< h3 class = "text-lg font-semi bold" : class = "isDark ? 'text-white' : 'text-gray-900 dark:text-white'" :style = "titleStyle" >
<!-- 左栏 : 分组标题 ( 桌面端吸顶 ) -- >
< div class = "mb-24px md:sticky md:top-120px md:mb-0 " >
< h3 class = "text-xl md:text-24px font-bold" : class = "isDark ? 'text-white' : 'text-gray-900 dark:text-white'" >
{ { isEn ? group . title _en : group . title _zh } }
< / h3 >
< / div >
<!-- 参数项列表 -- >
< div class = "px-32px" >
< div
v-for = "item in visibleItems(group)"
:key = "item.id"
class = "flex items-start justify-between gap-24 px py-14 px border-b last:border-b-0 "
: class = "isDark ? 'border-white/5' : 'border-gray-50 dark:border-dark-700/50'"
>
< span class = "text-sm shrink-0" : class = "isDark ? 'text-gray-400' : 'text-gray-500 dark:text-gray-400'" >
{ { isEn ? item . name _en : item . name _zh } }
< / span >
< span class = "text-sm text-right font-medium" : class = "isDark ? 'text-gray-200' : 'text-gray-800 dark:text-gray-200'" >
{ { isEn ? item . value _en : item . value _zh } }
< / span >
< / div >
< / div >
<!-- 右栏 : 参数卡片 ( 2 列 : 参数名 / 参数值 ) -- >
< div
class = "rounded-16px overflow-hidden"
: class = "isDark ? 'bg-white/5 border border-white/10' : 'bg-gray-100 border border-gray-200 dark:bg-dark-800 dark:border-dark-700'"
>
< div class = "px-24px md:px-32 px py-16 px" >
< div
v-for = "(item, idx) in fixedItems(group)"
:key = "item.id"
class = "flex items-baseline justify-between gap-24px py-16px border-b"
: class = "[isDark ? 'border-white/10' : 'border-gray-300 dark:border-dark-600', idx === fixedItems(group).length - 1 && (!needToggle(group) || !isExpanded(group)) ? 'border-b-0' : '']"
>
< span class = "text-sm font-semibold w-20% shrink-0" : class = "isDark ? 'text-gray-200' : 'text-gray-800 dark:text-gray-200'" >
{ { isEn ? item . name _en : item . name _zh } }
< / span >
< span class = "flex-1 text-sm text-left" : class = "isDark ? 'text-gray-400' : 'text-gray-500 dark:text-gray-400'" >
{ { isEn ? item . value _en : item . value _zh } }
< / span >
< / div >
<!-- 展开 / 收起按钮 -- >
< div v-if = "needToggle(group)" class="px-32px py-16px ">
< button
class = "text-sm font-medium transition-colors"
: class = "isDark ? 'text-primary-300 hover:text-primary-200' : 'text-primary hover:opacity-80'"
@click ="toggle(group)"
>
{ { isExpanded ( group ) ? '收起参数 ∧' : ` 查看全部参数( ${ group . items ? . length ? ? 0 } 项)∨ ` } }
< / button >
<!-- 收起时隐藏的参数项 : 高度过渡动画 -- >
< Transition @enter ="onEnter" @ after -enter = " onAfterEnter " @leave ="onLeave ">
< div v-if = "isExpanded(group)" >
< div
v-for = "item in group.items?.slice(group.default_visible || 5)"
:key = "item.id"
class = "flex items-baseline justify-between gap-24px py-16px border-b"
: class = "[isDark ? 'border-white/10' : 'border-gray-300 dark:border-dark-600', item.id === group.items?.at(-1)?.id ? 'border-b-0' : '']"
>
< span class = "text-sm font-semibold w-20% shrink-0" : class = "isDark ? 'text-gray-200' : 'text-gray-800 dark:text-gray-200'" >
{ { isEn ? item . name _en : item . name _zh } }
< / span >
< span class = "flex-1 text-sm text-left" : class = "isDark ? 'text-gray-400' : 'text-gray-500 dark:text-gray-400'" >
{ { isEn ? item . value _en : item . value _zh } }
< / span >
< / div >
< / div >
< / Transition >
< / div >
<!-- 展开 / 收起按钮 -- >
< div v-if = "needToggle(group)" class="px-24px md:px-32px py-16px flex justify-center" >
< button
class = "text-sm font-medium inline-flex items-center gap-8px bg-transparent cursor-pointer transition-colors"
: class = "isDark ? 'text-white' : 'text-gray-600 dark:text-white'"
@click ="toggle(group)"
>
< template v-if = "isExpanded(group)" >
{{ isEn ? ' View less ' : ' 收起参数 ' }}
< span class = "w-24px h-24px rounded-full flex items-center justify-center text-14px" : class = "isDark ? 'bg-white/10 text-white' : 'bg-gray-200 text-gray-700'" aria -hidden = " true " >
< Icon icon = "mingcute:up-line" / >
< / span >
< / template >
< template v-else >
{{ isEn ? ` View all ( $ { group.items ? .length ? ? 0 } ) ` : ` 查看全部参数 ( $ { group.items ? .length ? ? 0 } 项 ) ` }}
< span class = "w-24px h-24px rounded-full flex items-center justify-center text-14px" : class = "isDark ? 'bg-white/10 text-white' : 'bg-gray-200 text-gray-700'" aria -hidden = " true " >
< Icon icon = "mingcute:down-line" / >
< / span >
< / template >
< / button >
< / div >
< / div >
< / div >
< / div >