docs/archives/124-navigation-optimization/experience.md
核心思想: 在动态布局中通过固定关键元素确保整体稳定性
应用场景:
实施要点:
<!-- ✅ 正确模式:锚点策略 -->
<!-- 条件元素放在锚点前 -->
<Button v-if="condition" />
<!-- 锚点元素始终渲染 -->
<Button class="layout-anchor" :class="{ active: condition }" />
<!-- 锚点后的元素位置稳定 -->
<Button />
<!-- ❌ 错误模式:条件渲染导致位移 -->
<Button />
<Button v-if="condition" /> <!-- 会影响后续元素位置 -->
<Button />
设计原则:
可复用性: 这个模式可应用于所有涉及条件显示的UI布局设计。
核心理念: 通过视觉权重区分功能重要性,优化用户认知负担
分层标准:
// 功能分层配置
const UI_LAYERS = {
// 核心功能:用户主要操作路径
core: {
type: 'default',
size: 'medium',
ghost: false,
weight: 'high'
},
// 辅助功能:设置和次要操作
auxiliary: {
type: 'quaternary',
size: 'small',
ghost: true,
weight: 'low'
}
}
视觉权重控制:
用户体验效果:
统一原则: "一个功能,一个组件"
实施策略:
<!-- ❌ 避免:混用不同组件 -->
<NButton>操作A</NButton>
<ActionButtonUI>操作B</ActionButtonUI>
<CustomButton>操作C</CustomButton>
<!-- ✅ 推荐:统一组件,配置区分 -->
<ActionButtonUI type="default">操作A</ActionButtonUI>
<ActionButtonUI type="secondary">操作B</ActionButtonUI>
<ActionButtonUI type="quaternary">操作C</ActionButtonUI>
配置标准化:
// 建立配置预设
const BUTTON_PRESETS = {
navigation: {
type: 'default',
size: 'medium',
ghost: false,
round: true
},
auxiliary: {
type: 'quaternary',
size: 'small',
ghost: true
}
}
长期收益:
核心思路: 在不破坏现有功能的基础上逐步改进架构
实施路径:
风险控制:
// 渐进式导出策略
export { default as LanguageSwitchDropdown } from './components/LanguageSwitchDropdown.vue'
export {
default as LanguageSwitch,
/** @deprecated Use LanguageSwitchDropdown instead */
} from './components/LanguageSwitch.vue'
经验教训: 急于删除旧组件往往导致意外的依赖问题,渐进式升级更安全可靠。
集成策略: 充分利用组件库能力,避免重复造轮子
最佳实践:
<!-- ✅ 正确:利用NDropdown原生能力 -->
<NDropdown
:options="languageOptions"
@select="handleLanguageSelect"
>
<NButton quaternary>
<template #icon>
<span class="text-lg">🌐</span>
</template>
</NButton>
</NDropdown>
<!-- ❌ 避免:重新实现下拉逻辑 -->
<div class="custom-dropdown">
<!-- 手动实现下拉菜单逻辑 -->
</div>
组件选型原则:
经验积累: Naive UI组件质量很高,大部分场景下直接使用比自定义实现更优。
状态管理模式:
// ✅ 推荐:reactive + computed的清晰模式
const state = reactive({
currentLanguage: 'zh-CN',
availableLanguages: []
})
const languageOptions = computed(() =>
state.availableLanguages.map(lang => ({
key: lang.code,
label: lang.name
}))
)
// ❌ 避免:过度使用ref导致解包混乱
const currentLanguage = ref('zh-CN')
const availableLanguages = ref([])
const languageOptions = ref([]) // 手动维护衍生状态
生命周期使用:
// 服务注入和初始化的标准模式
const preferences = inject('preferenceService')
onMounted(async () => {
// 组件挂载后初始化
if (preferences) {
const saved = await preferences.getLanguage()
if (saved) {
state.currentLanguage = saved
}
}
})
错误处理模式:
const handleLanguageSelect = async (key: string) => {
try {
// 业务逻辑
setLocale(key)
await preferences?.setLanguage(key)
} catch (error) {
// 用户友好的错误处理
console.error('Language switch failed:', error)
// 可选:显示错误提示
message.error('语言切换失败,请重试')
}
}
响应式策略: 组件内置响应式 > 媒体查询 > JavaScript动态计算
组件内置响应式:
<!-- ✅ 最优:利用组件内置特性 -->
<ActionButtonUI
icon="⚙️"
text="设置"
<!-- 组件内部自动处理:max-md:hidden -->
/>
<!-- ✅ 可选:TailwindCSS媒体查询 -->
<span class="hidden md:inline">设置</span>
<!-- ❌ 避免:JavaScript动态控制 -->
<span v-if="!isMobile">设置</span>
断点设计原则:
/* 移动优先的断点策略 */
.navigation-button {
/* 移动端基础样式 */
@media (min-width: 768px) {
/* 平板样式 */
}
@media (min-width: 1024px) {
/* 桌面样式 */
}
}
测试覆盖策略: 确保在关键断点处进行实际设备测试。
接口设计原则:
// ✅ 清晰的接口定义
interface LanguageOption {
key: string // 必需:locale代码
label: string // 必需:显示名称
flag?: string // 可选:图标
}
interface LanguageSwitchProps {
options?: LanguageOption[] // 可选:默认使用内置选项
showFlags?: boolean // 可选:是否显示图标
}
// ❌ 避免:模糊的类型定义
interface SomeProps {
data?: any
config?: object
}
类型复用策略:
// 建立类型复用体系
export type ButtonType = 'default' | 'primary' | 'secondary' | 'tertiary' | 'quaternary'
export type ButtonSize = 'small' | 'medium' | 'large'
interface BaseButtonProps {
type?: ButtonType
size?: ButtonSize
ghost?: boolean
}
常见错误: 在布局关键位置使用v-if
<!-- ❌ 危险:会导致布局跳动 -->
<div class="navigation">
<Button>固定按钮1</Button>
<Button v-if="condition">条件按钮</Button> <!-- 位置不稳定 -->
<Button>固定按钮2</Button> <!-- 会随条件按钮跳动 -->
</div>
正确做法: 使用样式控制可见性或锚点策略
<!-- ✅ 安全:保持DOM结构稳定 -->
<div class="navigation">
<Button>固定按钮1</Button>
<Button :class="{ invisible: !condition }">条件按钮</Button>
<Button>固定按钮2</Button> <!-- 位置稳定 -->
</div>
常见错误: 急于删除旧组件导出
// ❌ 危险:可能存在隐藏依赖
// export { default as OldComponent } from './OldComponent.vue' // 直接删除
安全做法: 渐进式清理
// ✅ 安全:保留并标记deprecated
export {
default as OldComponent,
/** @deprecated Use NewComponent instead. Will be removed in next major version. */
} from './OldComponent.vue'
清理检查清单:
常见问题: 主题切换时图标颜色被覆盖
/* 问题:全局CSS覆盖了组件样式 */
.icon {
color: currentColor !important; /* 过强的权重 */
}
解决策略:
<!-- 方案1:内联样式优先级最高 -->
<NIcon :style="{ color: iconColor }">
<!-- 方案2:更具体的CSS选择器 -->
<NIcon class="theme-icon">
.theme-icon {
color: var(--theme-color) !important;
}
常见遗漏: 只在浏览器开发工具中测试响应式
// ❌ 不充分:仅模拟器测试
browser.setViewportSize({ width: 375, height: 812 })
完整测试: 真实设备验证
// ✅ 完整:多设备尺寸 + 真实设备测试
const testSizes = [
{ width: 375, height: 812, name: 'iPhone' },
{ width: 768, height: 1024, name: 'iPad' },
{ width: 1920, height: 1080, name: 'Desktop' }
]
// 额外:真实设备测试
// 1. iPhone实际测试
// 2. Android设备测试
// 3. 不同浏览器测试
问题: 不同语言文本长度差异巨大
<!-- 问题:德语文本可能比中文长3倍 -->
<Button>{{ $t('nav.settings') }}</Button>
<!-- 中文:"设置" (2字符) -->
<!-- 德语:"Einstellungen" (12字符) -->
解决方案:
<!-- CSS处理文本溢出 -->
<Button class="nav-button">
{{ $t('nav.settings') }}
</Button>
.nav-button {
min-width: 120px; /* 为长文本预留空间 */
text-overflow: ellipsis; /* 溢出显示省略号 */
overflow: hidden;
}
设计目标: 减少代码重复,统一维护入口
实施模式: "单一源码,多端部署"
# Web版本(主实现)
packages/web/src/App.vue
# Extension版本(复用)
cp packages/web/src/App.vue packages/extension/src/App.vue
# 好处:
# 1. 统一的bug修复
# 2. 一致的功能更新
# 3. 降低维护成本
适用场景判断:
分层原则:
UI组件库 (Naive UI)
↓
封装组件层 (ActionButtonUI, LanguageSwitchDropdown)
↓
业务组件层 (App.vue, MainLayout)
↓
页面应用层 (Web, Extension, Desktop)
职责划分:
设计收益:
核心思想: 通过配置而非代码修改支持功能扩展
语言扩展示例:
// ✅ 配置驱动:添加新语言只需修改配置
const AVAILABLE_LANGUAGES = [
{ key: 'zh-CN', label: '简体中文', flag: '🇨🇳' },
{ key: 'en-US', label: 'English', flag: '🇺🇸' },
{ key: 'ja-JP', label: '日本語', flag: '🇯🇵' } // 新增
]
// ❌ 硬编码:添加新语言需要修改多处代码
const toggleLanguage = () => {
if (current === 'zh-CN') return 'en-US'
if (current === 'en-US') return 'ja-JP'
if (current === 'ja-JP') return 'zh-CN'
}
扩展点设计原则:
容错设计: 组件在异常情况下的行为
<template>
<!-- 主要功能 -->
<LanguageSwitchDropdown v-if="servicesReady" />
<!-- 降级功能 -->
<NButton v-else disabled>
{{ $t('common.loading') }}
</NButton>
</template>
<script>
// 错误处理
const handleLanguageSwitch = async (lang) => {
try {
await switchLanguage(lang)
} catch (error) {
// 降级:不阻断用户操作,记录错误
console.error('Language switch failed, using client fallback')
useClientOnlyLanguageSwitch(lang)
}
}
</script>
降级策略制定:
经验总结: 本项目通过系统化的设计和实施,不仅解决了具体的用户体验问题,更重要的是建立了一套可复用的设计模式和最佳实践。这些经验可以直接应用于后续的UI优化工作,显著提升开发效率和产品质量。
核心价值: 从单点问题解决升华为系统性能力建设,为团队积累了宝贵的技术资产。