SelectCascader 组件设计文档
概述
SelectCascader 组件是本组件库中用于级联选择的交互组件。它延续了我们组件库一贯的设计理念,采用"关注点分离"和"逻辑抽象"的原则,通过 useSelectCascader 等核心逻辑实现了一套完整且易用的级联选择解决方案。
SelectCascader 组件专注于处理用户的级联选择操作,为用户提供了一套直观、美观的多级联动选择功能,支持自定义尺寸、禁用状态、多级菜单和选项高亮等功能。
设计理念
分层架构
SelectCascader 组件严格按照三层架构设计:
- 结构层 selectCascader.vue - 负责组件的 DOM 结构和基本布局
- 逻辑层 selectCascaderComputed.ts - 处理复杂的响应式计算和交互逻辑
- 表现层 selectCascader.scss - 定义组件的视觉呈现和样式规则
逻辑抽象
SelectCascader 组件将复杂的逻辑抽象为核心函数:
- useSelectCascader - 处理级联选择器的核心逻辑
- useStyleComputed - 处理样式计算
这种设计使得组件本身只需要声明所需逻辑,而具体的计算由逻辑函数完成。
核心功能设计
Props 结构化设计
SelectCascader 组件的 props 设计简洁明了:
typescript
export const selectCascaderProps = {
modelValue: {
type: [String, Number] as PropType<string | number>,
default: ''
},
placeholder: {
type: String,
default: '请选择'
},
disabled: {
type: Boolean,
default: false
},
width: {
type: [String, Number] as PropType<string | number>,
default: '260px'
},
height: {
type: [String, Number] as PropType<string | number>,
default: '40px'
},
options: { // 添加 options 属性
type: Array as PropType<Option[]> | undefined,
required: true
}
};这种设计提供了足够的灵活性,支持多种使用场景。
数据结构设计
SelectCascader 组件使用树形数据结构:
typescript
export interface Option {
value: string | number;
label: string;
disabled?: boolean;
children?: Option[];
}这种结构支持无限层级的级联选择。
样式系统设计
CSS 样式控制
SelectCascader 组件通过 SCSS 实现样式控制:
- 深色主题 - 默认采用深色背景和浅色文本
- 悬停效果 - 鼠标悬停时的背景色变化
- 聚焦效果 - 获得焦点时的边框高亮
- 禁用状态 - 选项禁用时的视觉效果
尺寸系统
SelectCascader 组件支持灵活的尺寸控制:
- 默认尺寸 - 260px × 40px
- 自定义尺寸 - 支持 px、em、% 等单位
- 响应式设计 - 支持不同屏幕尺寸适配
状态样式
SelectCascader 组件根据不同状态提供相应的样式:
- 默认状态 - 标准下拉框样式
- 悬停状态 - 鼠标悬停时的样式
- 聚焦状态 - 获得焦点时的样式
- 禁用状态 - 要用时的半透明效果
- 展开状态 - 下拉框展开时的箭头旋转效果
组件结构设计
整体结构
SelectCascader 组件的整体结构如下:
html
<div class="select-e" :class="{ 'is-focused': isFocused, 'is-disabled': disabled }" tabindex="0"
:style="selectStyle" @focus="handleFocus" @blur="handleBlur">
<div class="select-trigger" :class="{ 'is-disabled': disabled }" @click="toggleDropdown">
<span class="selected-value">{{ selectedLabel || placeholder }}</span>
<span class="arrow-icon" :class="{ 'is-open': dropdownVisible }">
<svg viewBox="0 0 24 24" width="16" height="16">
<path d="M7 10l5 5 5-5z" />
</svg>
</span>
</div>
<div ref="dropdownContainer" class="dropdown-container" @mouseleave="handleContainerMouseleave">
<Transition name="slide-fade">
<div v-show="dropdownVisible" ref="selectDropdown" class="select-dropdown" :style="dropdownPosition">
<MYScrollbar height="200px" thumbColor="#4C4D4F" thumbHoverColor="#2a2a2e"
trackColor="#2a2a2e">
<div v-for="item in options" :key="item.value" class="select-option"
:class="{ 'is-disabled': item.disabled || disabled }" @mouseenter="() => handleMouseenter(item)">
<div class="label" @click="() => handleClick(item)">
{{ item.label }}
<span v-if="item.children" class="arrow-icon">▶</span>
</div>
</div>
</MYScrollbar>
</div>
</Transition>
<!-- 二级菜单 -->
<Transition name="slide-fade">
<div v-if="activeSubMenu" class="sub-menu" :style="subMenuPosition">
<div v-for="child in activeSubMenuItems" :key="child.value" class="select-option"
:class="{ 'is-disabled': child.disabled || disabled }" @click.stop="handleClick(child)" @mouseenter="() => handleSecondLevelEnter(child)">
<div class="label">
{{ child.label }}
<span v-if="child.children" class="arrow-icon">▶</span>
</div>
</div>
</div>
</Transition>
<!-- 三级菜单 -->
<Transition name="slide-fade">
<div v-if="activeThirdMenu" class="third-menu" :style="thirdMenuPosition">
<div v-for="third in activeThirdMenuItems" :key="third.value" class="select-option"
:class="{ 'is-disabled': (third as any).disabled || disabled }" @click.stop="handleClick(third)">
<div class="label">{{ third.label }}</div>
</div>
</div>
</Transition>
</div>
</div>菜单结构
各级菜单结构统一:
html
<div v-for="item in options" :key="item.value" class="select-option"
:class="{ 'is-disabled': item.disabled || disabled }" @mouseenter="() => handleMouseenter(item)">
<div class="label" @click="() => handleClick(item)">
{{ item.label }}
<span v-if="item.children" class="arrow-icon">▶</span>
</div>
</div>交互设计
下拉展开/收起
SelectCascader 组件支持点击展开/收起下拉框:
typescript
const toggleDropdown = () => {
if (props.disabled) return
dropdownVisible.value = !dropdownVisible.value
if (!dropdownVisible.value) {
activeSubMenu.value = ''
activeThirdMenu.value = null
} else {
nextTick(() => {
updateSubMenuPosition()
updateThirdMenuPosition()
})
}
}级联选择
SelectCascader 组件支持多级联动选择:
typescript
const handleMouseenter = (item: Option) => {
if (props.disabled || item.disabled) return
if (item.children) {
activeSubMenu.value = item.value.toString()
activeOption.value = item
nextTick(() => updateSubMenuPosition())
} else {
activeSubMenu.value = ''
activeThirdMenu.value = null
activeOption.value = null
}
}选项选择
SelectCascader 组件支持点击选择选项:
typescript
const handleClick = (item: Option) => {
if (props.disabled || item.disabled) return
selectedLabel.value = item.label
dropdownVisible.value = false
activeSubMenu.value = ''
activeThirdMenu.value = null
emit('update:modelValue', item.value)
}扩展性设计
SelectCascader 组件具备良好的扩展性:
- 自定义尺寸:通过 width 和 height 属性控制组件尺寸
- 禁用状态:通过 disabled 属性控制禁用状态
- 数据驱动:通过 option 属性驱动多级菜单显示
- 样式定制:通过 CSS 变量支持样式定制
- 主题适配:通过 CSS 变量支持主题定制
样式细节
SelectCascader 组件在样式处理上有以下特点:
- 过渡动画:下拉框展开/收起时具有平滑的过渡效果
- 响应式设计:支持灵活的尺寸适配
- 无障碍设计:支持键盘导航和屏幕阅读器
- 一致性:与组件库其他表单组件保持样式一致性
- 高亮显示:选中选项具有明显的高亮效果
- 级联指示:子菜单项具有明确的指示箭头
性能优化
通过以下方式优化性能:
- 使用 computed 属性缓存计算结果
- 将复杂逻辑移至独立的函数中
- 避免不必要的重新渲染
- 使用 Vue 3 Composition API 提高运行效率
- 合理使用事件监听器的添加和移除
- 延迟计算菜单位置
设计价值
SelectCascader 组件延续了我们组件库的设计哲学:
- 关注点分离:结构、样式、逻辑完全解耦
- 高度复用:通过函数抽象通用逻辑
- 易于维护:清晰的代码结构和命名规范
- 可扩展性强:支持丰富的定制选项
- 一致性:与 Select、SelectTime、SelectDate、PickColor、Rate、Switch、Checkbox、Radio 等其他组件保持一致的设计语言