FontSizeSwitch.vue是字体大小切换组件,提供用户调节系统字体大小的功能,支持小、中、大三种字体大小选择。
文件位置: src/components/common/FontSizeSwitch.vue
- 支持三种字体大小:小、中、大
- 实时预览字体大小变化
- 状态持久化保存
- 全局字体大小应用
- 响应式字体调节
<template>
<div class="font-size-switch">
<el-dropdown
@command="handleCommand"
trigger="click"
placement="bottom-end"
>
<div class="font-size-trigger">
<el-icon class="font-size-icon">
<component :is="currentIcon" />
</el-icon>
<span class="font-size-text">{{ currentSizeText }}</span>
<el-icon class="dropdown-icon">
<ArrowDown />
</el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="size in fontSizes"
:key="size.value"
:command="size.value"
:class="{ 'is-active': currentSize === size.value }"
>
<div class="font-size-option">
<el-icon class="option-icon">
<component :is="size.icon" />
</el-icon>
<span class="option-text" :style="{ fontSize: size.previewSize }">
{{ size.label }}
</span>
<el-icon v-if="currentSize === size.value" class="check-icon">
<Check />
</el-icon>
</div>
</el-dropdown-item>
<el-dropdown-item divided command="reset">
<div class="font-size-option">
<el-icon class="option-icon">
<RefreshLeft />
</el-icon>
<span class="option-text">重置默认</span>
</div>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template><script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { useLayoutStore } from '@/stores/layout'
import {
ArrowDown,
Check,
RefreshLeft,
ZoomIn,
Minus,
Plus
} from '@element-plus/icons-vue'
// 状态管理
const layoutStore = useLayoutStore()
// 字体大小配置
const fontSizes = ref([
{
value: 'small',
label: '小号字体',
icon: 'Minus',
previewSize: '12px',
cssSize: '12px',
description: '适合高分辨率屏幕'
},
{
value: 'medium',
label: '标准字体',
icon: 'ZoomIn',
previewSize: '14px',
cssSize: '14px',
description: '推荐使用的标准大小'
},
{
value: 'large',
label: '大号字体',
icon: 'Plus',
previewSize: '16px',
cssSize: '16px',
description: '适合视力不佳的用户'
}
])
// 当前字体大小
const currentSize = computed(() => layoutStore.fontSize)
// 当前字体大小信息
const currentSizeInfo = computed(() => {
return fontSizes.value.find(size => size.value === currentSize.value) || fontSizes.value[1]
})
// 当前图标
const currentIcon = computed(() => currentSizeInfo.value.icon)
// 当前大小文本
const currentSizeText = computed(() => currentSizeInfo.value.label)
</script>// 处理命令
const handleCommand = (command) => {
if (command === 'reset') {
resetFontSize()
} else {
setFontSize(command)
}
}
// 设置字体大小
const setFontSize = (size) => {
// 更新状态
layoutStore.setFontSize(size)
// 应用字体大小
applyFontSize(size)
// 显示提示
showFontSizeNotification(size)
}
// 应用字体大小
const applyFontSize = (size) => {
const sizeInfo = fontSizes.value.find(s => s.value === size)
if (!sizeInfo) return
// 设置CSS变量
document.documentElement.style.setProperty('--base-font-size', sizeInfo.cssSize)
document.documentElement.setAttribute('data-font-size', size)
// 更新Element Plus组件大小
updateElementPlusSize(size)
// 触发字体大小变化事件
window.dispatchEvent(new CustomEvent('font-size-change', {
detail: { size, sizeInfo }
}))
}
// 更新Element Plus组件大小
const updateElementPlusSize = (size) => {
const sizeMap = {
small: 'small',
medium: 'default',
large: 'large'
}
const elSize = sizeMap[size] || 'default'
// 更新全局配置
if (window.$ELEMENT) {
window.$ELEMENT.size = elSize
}
// 更新CSS类
document.documentElement.className = document.documentElement.className
.replace(/el-size-\w+/g, '')
document.documentElement.classList.add(`el-size-${elSize}`)
}
// 重置字体大小
const resetFontSize = () => {
setFontSize('medium')
}
// 显示字体大小通知
const showFontSizeNotification = (size) => {
const sizeInfo = fontSizes.value.find(s => s.value === size)
if (!sizeInfo) return
ElMessage({
message: `字体大小已切换为:${sizeInfo.label}`,
type: 'success',
duration: 2000,
showClose: false
})
}// 快捷键处理
const handleKeyboard = (event) => {
// Ctrl + 加号:增大字体
if (event.ctrlKey && event.key === '=') {
event.preventDefault()
increaseFontSize()
}
// Ctrl + 减号:减小字体
if (event.ctrlKey && event.key === '-') {
event.preventDefault()
decreaseFontSize()
}
// Ctrl + 0:重置字体
if (event.ctrlKey && event.key === '0') {
event.preventDefault()
resetFontSize()
}
}
// 增大字体
const increaseFontSize = () => {
const sizes = ['small', 'medium', 'large']
const currentIndex = sizes.indexOf(currentSize.value)
const nextIndex = Math.min(currentIndex + 1, sizes.length - 1)
if (nextIndex !== currentIndex) {
setFontSize(sizes[nextIndex])
} else {
ElMessage.warning('字体大小已达到最大值')
}
}
// 减小字体
const decreaseFontSize = () => {
const sizes = ['small', 'medium', 'large']
const currentIndex = sizes.indexOf(currentSize.value)
const prevIndex = Math.max(currentIndex - 1, 0)
if (prevIndex !== currentIndex) {
setFontSize(sizes[prevIndex])
} else {
ElMessage.warning('字体大小已达到最小值')
}
}
// 绑定键盘事件
onMounted(() => {
document.addEventListener('keydown', handleKeyboard)
})
// 清理键盘事件
onUnmounted(() => {
document.removeEventListener('keydown', handleKeyboard)
})// 字体大小预设配置
const fontSizePresets = {
small: {
base: '12px',
h1: '20px',
h2: '18px',
h3: '16px',
h4: '14px',
h5: '12px',
h6: '11px',
button: '12px',
input: '12px',
table: '11px'
},
medium: {
base: '14px',
h1: '24px',
h2: '20px',
h3: '18px',
h4: '16px',
h5: '14px',
h6: '13px',
button: '14px',
input: '14px',
table: '13px'
},
large: {
base: '16px',
h1: '28px',
h2: '24px',
h3: '20px',
h4: '18px',
h5: '16px',
h6: '15px',
button: '16px',
input: '16px',
table: '15px'
}
}
// 应用字体大小预设
const applyFontSizePreset = (size) => {
const preset = fontSizePresets[size]
if (!preset) return
// 应用各种元素的字体大小
Object.keys(preset).forEach(key => {
document.documentElement.style.setProperty(`--font-size-${key}`, preset[key])
})
}// 响应式字体调节
const adjustFontSizeForDevice = () => {
const device = layoutStore.device
const screenWidth = window.innerWidth
// 根据设备类型调整字体大小
let adjustedSize = currentSize.value
if (device === 'mobile') {
// 移动端字体稍大一些
if (currentSize.value === 'small') {
adjustedSize = 'medium'
}
} else if (device === 'tablet') {
// 平板端保持原有大小
adjustedSize = currentSize.value
} else {
// 桌面端根据屏幕宽度调整
if (screenWidth > 1920) {
// 高分辨率屏幕,字体可以稍大
if (currentSize.value === 'small') {
adjustedSize = 'medium'
} else if (currentSize.value === 'medium') {
adjustedSize = 'large'
}
}
}
// 应用调整后的字体大小
if (adjustedSize !== currentSize.value) {
applyFontSize(adjustedSize)
}
}
// 监听设备变化
watch(
() => layoutStore.device,
() => {
adjustFontSizeForDevice()
}
)// 字体大小切换动画
const animateFontSizeChange = (fromSize, toSize) => {
const duration = 300
const startTime = Date.now()
const fromInfo = fontSizes.value.find(s => s.value === fromSize)
const toInfo = fontSizes.value.find(s => s.value === toSize)
if (!fromInfo || !toInfo) return
const fromPx = parseInt(fromInfo.cssSize)
const toPx = parseInt(toInfo.cssSize)
const animate = () => {
const elapsed = Date.now() - startTime
const progress = Math.min(elapsed / duration, 1)
// 使用缓动函数
const easeProgress = easeInOutCubic(progress)
// 计算当前字体大小
const currentPx = fromPx + (toPx - fromPx) * easeProgress
// 应用字体大小
document.documentElement.style.setProperty('--base-font-size', `${currentPx}px`)
if (progress < 1) {
requestAnimationFrame(animate)
}
}
animate()
}
// 缓动函数
const easeInOutCubic = (t) => {
return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
}.font-size-switch {
display: inline-block;
}
.font-size-trigger {
display: flex;
align-items: center;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
user-select: none;
&:hover {
background: var(--el-fill-color-light);
}
}
.font-size-icon {
margin-right: 6px;
font-size: 16px;
color: var(--el-text-color-regular);
}
.font-size-text {
font-size: 13px;
color: var(--el-text-color-regular);
margin-right: 4px;
}
.dropdown-icon {
font-size: 12px;
color: var(--el-text-color-placeholder);
transition: transform 0.2s ease;
}
.font-size-trigger:hover .dropdown-icon {
transform: rotate(180deg);
}.font-size-option {
display: flex;
align-items: center;
width: 100%;
.option-icon {
margin-right: 8px;
font-size: 14px;
color: var(--el-text-color-regular);
}
.option-text {
flex: 1;
transition: font-size 0.2s ease;
}
.check-icon {
margin-left: 8px;
font-size: 14px;
color: var(--el-color-primary);
}
}
.el-dropdown-menu__item.is-active {
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
.option-icon {
color: var(--el-color-primary);
}
}/* 字体大小CSS变量 */
:root {
--font-size-base: 14px;
--font-size-h1: 24px;
--font-size-h2: 20px;
--font-size-h3: 18px;
--font-size-h4: 16px;
--font-size-h5: 14px;
--font-size-h6: 13px;
--font-size-button: 14px;
--font-size-input: 14px;
--font-size-table: 13px;
}
/* 小号字体 */
[data-font-size="small"] {
--font-size-base: 12px;
--font-size-h1: 20px;
--font-size-h2: 18px;
--font-size-h3: 16px;
--font-size-h4: 14px;
--font-size-h5: 12px;
--font-size-h6: 11px;
--font-size-button: 12px;
--font-size-input: 12px;
--font-size-table: 11px;
}
/* 大号字体 */
[data-font-size="large"] {
--font-size-base: 16px;
--font-size-h1: 28px;
--font-size-h2: 24px;
--font-size-h3: 20px;
--font-size-h4: 18px;
--font-size-h5: 16px;
--font-size-h6: 15px;
--font-size-button: 16px;
--font-size-input: 16px;
--font-size-table: 15px;
}
/* 应用字体大小 */
body {
font-size: var(--font-size-base);
}
h1 { font-size: var(--font-size-h1); }
h2 { font-size: var(--font-size-h2); }
h3 { font-size: var(--font-size-h3); }
h4 { font-size: var(--font-size-h4); }
h5 { font-size: var(--font-size-h5); }
h6 { font-size: var(--font-size-h6); }
.el-button {
font-size: var(--font-size-button);
}
.el-input__inner {
font-size: var(--font-size-input);
}
.el-table {
font-size: var(--font-size-table);
}onMounted(() => {
// 应用当前字体大小
applyFontSize(currentSize.value)
// 绑定键盘事件
document.addEventListener('keydown', handleKeyboard)
// 监听字体大小变化事件
window.addEventListener('font-size-change', handleFontSizeChange)
})
// 处理字体大小变化事件
const handleFontSizeChange = (event) => {
const { size, sizeInfo } = event.detail
// 更新相关组件
updateRelatedComponents(size, sizeInfo)
}
// 更新相关组件
const updateRelatedComponents = (size, sizeInfo) => {
// 通知图表组件重新渲染
window.dispatchEvent(new Event('resize'))
// 更新表格列宽
const tables = document.querySelectorAll('.el-table')
tables.forEach(table => {
if (table.__vue__) {
table.__vue__.doLayout()
}
})
}onUnmounted(() => {
// 移除键盘事件监听
document.removeEventListener('keydown', handleKeyboard)
// 移除字体大小变化事件监听
window.removeEventListener('font-size-change', handleFontSizeChange)
})<template>
<div class="header-tools">
<FontSizeSwitch />
</div>
</template>
<script setup>
import FontSizeSwitch from '@/components/common/FontSizeSwitch.vue'
</script><script setup>
import { ref } from 'vue'
// 自定义字体大小配置
const customFontSizes = ref([
{ value: 'xs', label: '超小', cssSize: '10px' },
{ value: 'small', label: '小', cssSize: '12px' },
{ value: 'medium', label: '中', cssSize: '14px' },
{ value: 'large', label: '大', cssSize: '16px' },
{ value: 'xl', label: '超大', cssSize: '18px' }
])
</script><script setup>
import { watch } from 'vue'
import { useLayoutStore } from '@/stores/layout'
const layoutStore = useLayoutStore()
// 监听字体大小变化
watch(
() => layoutStore.fontSize,
(newSize, oldSize) => {
console.log(`字体大小从 ${oldSize} 切换到 ${newSize}`)
// 执行相关操作
handleFontSizeChange(newSize, oldSize)
}
)
const handleFontSizeChange = (newSize, oldSize) => {
// 重新计算布局
nextTick(() => {
// 触发窗口resize事件
window.dispatchEvent(new Event('resize'))
})
}
</script>- 性能: 字体大小切换时避免频繁的DOM操作
- 兼容性: 确保在不同浏览器中的字体渲染一致
- 可访问性: 提供键盘快捷键支持
- 用户体验: 提供平滑的切换动画
- 响应式: 在不同设备上合理调整字体大小
最后更新时间:2025-09-19