第62天:Element Plus Day.js 时间本地化配置
学习目标
- 掌握 Day.js 在 Element Plus 中的应用
- 学习时间本地化的配置方法
- 理解不同地区的时间格式差异
- 实现动态时间本地化切换
知识点概览
1. Day.js 基础概念
1.1 Day.js 简介
- 轻量级:仅 2KB 的现代化日期库
- API 兼容:与 Moment.js 类似的 API
- 插件系统:模块化设计,按需加载
- 国际化支持:内置多语言支持
1.2 Day.js 核心配置
typescript
// dayjs/config.ts
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
import relativeTime from 'dayjs/plugin/relativeTime'
import duration from 'dayjs/plugin/duration'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import weekOfYear from 'dayjs/plugin/weekOfYear'
import weekYear from 'dayjs/plugin/weekYear'
import advancedFormat from 'dayjs/plugin/advancedFormat'
import localeData from 'dayjs/plugin/localeData'
// 导入语言包
import 'dayjs/locale/zh-cn'
import 'dayjs/locale/en'
import 'dayjs/locale/ar'
import 'dayjs/locale/ja'
import 'dayjs/locale/ko'
import 'dayjs/locale/fr'
import 'dayjs/locale/de'
import 'dayjs/locale/es'
import 'dayjs/locale/ru'
// 注册插件
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(relativeTime)
dayjs.extend(duration)
dayjs.extend(customParseFormat)
dayjs.extend(weekOfYear)
dayjs.extend(weekYear)
dayjs.extend(advancedFormat)
dayjs.extend(localeData)
// Day.js 配置管理器
export class DayjsConfigManager {
private static instance: DayjsConfigManager
private currentLocale: string = 'en'
private currentTimezone: string = 'UTC'
private constructor() {}
static getInstance(): DayjsConfigManager {
if (!DayjsConfigManager.instance) {
DayjsConfigManager.instance = new DayjsConfigManager()
}
return DayjsConfigManager.instance
}
// 设置语言
setLocale(locale: string): void {
this.currentLocale = locale
dayjs.locale(locale)
}
// 获取当前语言
getLocale(): string {
return this.currentLocale
}
// 设置时区
setTimezone(timezone: string): void {
this.currentTimezone = timezone
}
// 获取当前时区
getTimezone(): string {
return this.currentTimezone
}
// 获取支持的语言列表
getSupportedLocales(): Array<{ code: string; name: string; nativeName: string }> {
return [
{ code: 'en', name: 'English', nativeName: 'English' },
{ code: 'zh-cn', name: 'Chinese (Simplified)', nativeName: '简体中文' },
{ code: 'ar', name: 'Arabic', nativeName: 'العربية' },
{ code: 'ja', name: 'Japanese', nativeName: '日本語' },
{ code: 'ko', name: 'Korean', nativeName: '한국어' },
{ code: 'fr', name: 'French', nativeName: 'Français' },
{ code: 'de', name: 'German', nativeName: 'Deutsch' },
{ code: 'es', name: 'Spanish', nativeName: 'Español' },
{ code: 'ru', name: 'Russian', nativeName: 'Русский' }
]
}
// 格式化日期
format(date: dayjs.ConfigType, format?: string): string {
const dayjsInstance = dayjs(date)
if (this.currentTimezone !== 'UTC') {
return dayjsInstance.tz(this.currentTimezone).format(format)
}
return dayjsInstance.format(format)
}
// 获取相对时间
fromNow(date: dayjs.ConfigType): string {
return dayjs(date).fromNow()
}
// 获取本地化数据
getLocaleData() {
return dayjs.localeData()
}
}
export default DayjsConfigManager.getInstance()
2. Element Plus 时间组件本地化
2.1 DatePicker 本地化配置
vue
<!-- LocalizedDatePicker.vue -->
<template>
<div class="localized-date-picker">
<div class="locale-selector">
<el-select v-model="currentLocale" @change="handleLocaleChange">
<el-option
v-for="locale in supportedLocales"
:key="locale.code"
:label="locale.nativeName"
:value="locale.code"
/>
</el-select>
</div>
<div class="date-picker-container">
<el-date-picker
v-model="selectedDate"
type="date"
:placeholder="$t('datePicker.selectDate')"
:format="dateFormat"
:value-format="valueFormat"
@change="handleDateChange"
/>
<el-date-picker
v-model="selectedDateTime"
type="datetime"
:placeholder="$t('datePicker.selectDateTime')"
:format="dateTimeFormat"
:value-format="valueFormat"
@change="handleDateTimeChange"
/>
<el-date-picker
v-model="dateRange"
type="daterange"
:range-separator="$t('datePicker.to')"
:start-placeholder="$t('datePicker.startDate')"
:end-placeholder="$t('datePicker.endDate')"
:format="dateFormat"
:value-format="valueFormat"
@change="handleRangeChange"
/>
</div>
<div class="time-display">
<h3>{{ $t('timeDisplay.title') }}</h3>
<div class="time-info">
<p><strong>{{ $t('timeDisplay.selected') }}:</strong> {{ formattedSelectedDate }}</p>
<p><strong>{{ $t('timeDisplay.now') }}:</strong> {{ formattedNow }}</p>
<p><strong>{{ $t('timeDisplay.relative') }}:</strong> {{ relativeTime }}</p>
<p><strong>{{ $t('timeDisplay.weekday') }}:</strong> {{ weekdayName }}</p>
<p><strong>{{ $t('timeDisplay.month') }}:</strong> {{ monthName }}</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import dayjs from 'dayjs'
import DayjsConfigManager from '@/utils/dayjs-config'
const { t, locale } = useI18n()
const dayjsConfig = DayjsConfigManager
// 响应式数据
const currentLocale = ref('en')
const selectedDate = ref('')
const selectedDateTime = ref('')
const dateRange = ref<[string, string]>(['', ''])
const now = ref(dayjs())
// 支持的语言
const supportedLocales = computed(() => dayjsConfig.getSupportedLocales())
// 日期格式配置
const dateFormats = {
'en': 'MM/DD/YYYY',
'zh-cn': 'YYYY年MM月DD日',
'ar': 'DD/MM/YYYY',
'ja': 'YYYY年MM月DD日',
'ko': 'YYYY년 MM월 DD일',
'fr': 'DD/MM/YYYY',
'de': 'DD.MM.YYYY',
'es': 'DD/MM/YYYY',
'ru': 'DD.MM.YYYY'
}
const dateTimeFormats = {
'en': 'MM/DD/YYYY HH:mm:ss',
'zh-cn': 'YYYY年MM月DD日 HH:mm:ss',
'ar': 'DD/MM/YYYY HH:mm:ss',
'ja': 'YYYY年MM月DD日 HH:mm:ss',
'ko': 'YYYY년 MM월 DD일 HH:mm:ss',
'fr': 'DD/MM/YYYY HH:mm:ss',
'de': 'DD.MM.YYYY HH:mm:ss',
'es': 'DD/MM/YYYY HH:mm:ss',
'ru': 'DD.MM.YYYY HH:mm:ss'
}
// 计算属性
const dateFormat = computed(() => dateFormats[currentLocale.value] || dateFormats['en'])
const dateTimeFormat = computed(() => dateTimeFormats[currentLocale.value] || dateTimeFormats['en'])
const valueFormat = computed(() => 'YYYY-MM-DD')
const formattedSelectedDate = computed(() => {
if (!selectedDate.value) return '-'
return dayjsConfig.format(selectedDate.value, dateFormat.value)
})
const formattedNow = computed(() => {
return dayjsConfig.format(now.value, dateTimeFormat.value)
})
const relativeTime = computed(() => {
if (!selectedDate.value) return '-'
return dayjsConfig.fromNow(selectedDate.value)
})
const weekdayName = computed(() => {
if (!selectedDate.value) return '-'
return dayjs(selectedDate.value).format('dddd')
})
const monthName = computed(() => {
if (!selectedDate.value) return '-'
return dayjs(selectedDate.value).format('MMMM')
})
// 方法
const handleLocaleChange = (newLocale: string) => {
dayjsConfig.setLocale(newLocale)
locale.value = newLocale
// 更新当前时间以反映语言变化
now.value = dayjs()
}
const handleDateChange = (value: string) => {
console.log('Date changed:', value)
}
const handleDateTimeChange = (value: string) => {
console.log('DateTime changed:', value)
}
const handleRangeChange = (value: [string, string]) => {
console.log('Range changed:', value)
}
// 定时更新当前时间
let timer: NodeJS.Timeout
onMounted(() => {
// 初始化语言
dayjsConfig.setLocale(currentLocale.value)
// 每秒更新时间
timer = setInterval(() => {
now.value = dayjs()
}, 1000)
})
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
// 监听语言变化
watch(currentLocale, (newLocale) => {
handleLocaleChange(newLocale)
})
</script>
<style scoped>
.localized-date-picker {
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
.locale-selector {
margin-bottom: 20px;
}
.date-picker-container {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 30px;
}
.time-display {
background: #f5f7fa;
padding: 20px;
border-radius: 8px;
}
.time-info {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.time-info p {
margin: 5px 0;
padding: 8px;
background: white;
border-radius: 4px;
}
@media (max-width: 768px) {
.time-info {
grid-template-columns: 1fr;
}
.date-picker-container {
gap: 10px;
}
}
</style>
2.2 时区处理组件
vue
<!-- TimezoneHandler.vue -->
<template>
<div class="timezone-handler">
<div class="timezone-selector">
<el-select v-model="currentTimezone" @change="handleTimezoneChange">
<el-option
v-for="timezone in supportedTimezones"
:key="timezone.value"
:label="timezone.label"
:value="timezone.value"
/>
</el-select>
</div>
<div class="time-zones-display">
<div
v-for="zone in displayTimezones"
:key="zone.timezone"
class="timezone-card"
>
<h4>{{ zone.name }}</h4>
<div class="time-info">
<p class="current-time">{{ zone.currentTime }}</p>
<p class="timezone-offset">{{ zone.offset }}</p>
<p class="timezone-abbr">{{ zone.abbreviation }}</p>
</div>
</div>
</div>
<div class="meeting-scheduler">
<h3>{{ $t('meetingScheduler.title') }}</h3>
<el-form :model="meetingForm" label-width="120px">
<el-form-item :label="$t('meetingScheduler.dateTime')">
<el-date-picker
v-model="meetingForm.dateTime"
type="datetime"
:placeholder="$t('meetingScheduler.selectDateTime')"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item :label="$t('meetingScheduler.timezone')">
<el-select v-model="meetingForm.timezone">
<el-option
v-for="timezone in supportedTimezones"
:key="timezone.value"
:label="timezone.label"
:value="timezone.value"
/>
</el-select>
</el-form-item>
</el-form>
<div v-if="meetingForm.dateTime" class="meeting-times">
<h4>{{ $t('meetingScheduler.convertedTimes') }}</h4>
<div
v-for="conversion in convertedMeetingTimes"
:key="conversion.timezone"
class="converted-time"
>
<span class="timezone-name">{{ conversion.name }}:</span>
<span class="converted-datetime">{{ conversion.dateTime }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import dayjs from 'dayjs'
import DayjsConfigManager from '@/utils/dayjs-config'
interface TimezoneInfo {
timezone: string
name: string
currentTime: string
offset: string
abbreviation: string
}
interface MeetingForm {
dateTime: string
timezone: string
}
const { t } = useI18n()
const dayjsConfig = DayjsConfigManager
// 响应式数据
const currentTimezone = ref('UTC')
const meetingForm = ref<MeetingForm>({
dateTime: '',
timezone: 'UTC'
})
// 支持的时区
const supportedTimezones = [
{ value: 'UTC', label: 'UTC (Coordinated Universal Time)' },
{ value: 'America/New_York', label: 'New York (EST/EDT)' },
{ value: 'America/Los_Angeles', label: 'Los Angeles (PST/PDT)' },
{ value: 'Europe/London', label: 'London (GMT/BST)' },
{ value: 'Europe/Paris', label: 'Paris (CET/CEST)' },
{ value: 'Asia/Tokyo', label: 'Tokyo (JST)' },
{ value: 'Asia/Shanghai', label: 'Shanghai (CST)' },
{ value: 'Asia/Dubai', label: 'Dubai (GST)' },
{ value: 'Australia/Sydney', label: 'Sydney (AEST/AEDT)' }
]
// 显示的时区
const displayTimezones = computed((): TimezoneInfo[] => {
const zones = ['UTC', 'America/New_York', 'Europe/London', 'Asia/Tokyo', 'Asia/Shanghai']
return zones.map(timezone => {
const now = dayjs().tz(timezone)
return {
timezone,
name: supportedTimezones.find(tz => tz.value === timezone)?.label.split(' ')[0] || timezone,
currentTime: now.format('YYYY-MM-DD HH:mm:ss'),
offset: now.format('Z'),
abbreviation: now.format('z')
}
})
})
// 转换后的会议时间
const convertedMeetingTimes = computed(() => {
if (!meetingForm.value.dateTime) return []
const baseMeeting = dayjs.tz(meetingForm.value.dateTime, meetingForm.value.timezone)
return supportedTimezones.map(timezone => {
const convertedTime = baseMeeting.tz(timezone.value)
return {
timezone: timezone.value,
name: timezone.label.split(' ')[0],
dateTime: convertedTime.format('YYYY-MM-DD HH:mm:ss')
}
})
})
// 方法
const handleTimezoneChange = (timezone: string) => {
dayjsConfig.setTimezone(timezone)
}
// 定时更新
let timer: NodeJS.Timeout
onMounted(() => {
// 每分钟更新时间显示
timer = setInterval(() => {
// 触发重新计算
currentTimezone.value = currentTimezone.value
}, 60000)
})
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<style scoped>
.timezone-handler {
padding: 20px;
max-width: 1000px;
margin: 0 auto;
}
.timezone-selector {
margin-bottom: 20px;
}
.time-zones-display {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.timezone-card {
background: #f5f7fa;
padding: 15px;
border-radius: 8px;
text-align: center;
}
.timezone-card h4 {
margin: 0 0 10px 0;
color: #409eff;
}
.current-time {
font-size: 16px;
font-weight: bold;
margin: 5px 0;
}
.timezone-offset,
.timezone-abbr {
font-size: 12px;
color: #666;
margin: 2px 0;
}
.meeting-scheduler {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.meeting-times {
margin-top: 20px;
padding: 15px;
background: #f9f9f9;
border-radius: 6px;
}
.converted-time {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.converted-time:last-child {
border-bottom: none;
}
.timezone-name {
font-weight: bold;
color: #333;
}
.converted-datetime {
color: #666;
}
</style>
3. 自定义时间格式化工具
typescript
// utils/time-formatter.ts
import dayjs from 'dayjs'
import DayjsConfigManager from './dayjs-config'
export interface TimeFormatOptions {
locale?: string
timezone?: string
format?: string
relative?: boolean
}
export class TimeFormatter {
private dayjsConfig = DayjsConfigManager
// 格式化日期时间
format(date: dayjs.ConfigType, options: TimeFormatOptions = {}): string {
const {
locale = this.dayjsConfig.getLocale(),
timezone = this.dayjsConfig.getTimezone(),
format = 'YYYY-MM-DD HH:mm:ss',
relative = false
} = options
let dayjsInstance = dayjs(date)
// 设置语言
if (locale !== this.dayjsConfig.getLocale()) {
dayjsInstance = dayjsInstance.locale(locale)
}
// 设置时区
if (timezone !== 'UTC') {
dayjsInstance = dayjsInstance.tz(timezone)
}
// 返回相对时间或格式化时间
return relative ? dayjsInstance.fromNow() : dayjsInstance.format(format)
}
// 获取本地化的日期格式
getLocalizedFormat(type: 'date' | 'datetime' | 'time', locale?: string): string {
const currentLocale = locale || this.dayjsConfig.getLocale()
const formats = {
'en': {
date: 'MM/DD/YYYY',
datetime: 'MM/DD/YYYY HH:mm:ss',
time: 'HH:mm:ss'
},
'zh-cn': {
date: 'YYYY年MM月DD日',
datetime: 'YYYY年MM月DD日 HH:mm:ss',
time: 'HH:mm:ss'
},
'ar': {
date: 'DD/MM/YYYY',
datetime: 'DD/MM/YYYY HH:mm:ss',
time: 'HH:mm:ss'
}
}
return formats[currentLocale]?.[type] || formats['en'][type]
}
// 解析多种格式的日期
parse(dateString: string, format?: string): dayjs.Dayjs | null {
try {
if (format) {
return dayjs(dateString, format)
}
return dayjs(dateString)
} catch (error) {
console.error('Date parsing error:', error)
return null
}
}
// 获取时间范围描述
getTimeRangeDescription(start: dayjs.ConfigType, end: dayjs.ConfigType, locale?: string): string {
const startDate = dayjs(start)
const endDate = dayjs(end)
const currentLocale = locale || this.dayjsConfig.getLocale()
const duration = endDate.diff(startDate)
const days = Math.floor(duration / (1000 * 60 * 60 * 24))
const hours = Math.floor((duration % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
const minutes = Math.floor((duration % (1000 * 60 * 60)) / (1000 * 60))
// 根据语言返回不同的描述
switch (currentLocale) {
case 'zh-cn':
return `${days}天 ${hours}小时 ${minutes}分钟`
case 'ar':
return `${days} أيام ${hours} ساعات ${minutes} دقائق`
default:
return `${days} days ${hours} hours ${minutes} minutes`
}
}
// 获取工作日信息
getWorkdayInfo(date: dayjs.ConfigType, locale?: string) {
const dateObj = dayjs(date)
const currentLocale = locale || this.dayjsConfig.getLocale()
return {
isWeekend: dateObj.day() === 0 || dateObj.day() === 6,
weekdayName: dateObj.locale(currentLocale).format('dddd'),
weekNumber: dateObj.week(),
quarter: dateObj.quarter(),
dayOfYear: dateObj.dayOfYear()
}
}
}
export default new TimeFormatter()
实践练习
练习 1:创建多语言日历组件
开发一个支持多语言的日历组件:
- 月份和星期的本地化显示
- 不同地区的周开始日配置
- 节假日标记功能
练习 2:实现时区转换工具
构建一个时区转换工具:
- 实时显示多个时区时间
- 会议时间安排助手
- 时差计算功能
练习 3:开发本地化时间选择器
设计一个智能时间选择器:
- 根据用户地区自动调整格式
- 支持多种时间输入方式
- 智能时间建议功能
学习资源
作业
- 完成所有实践练习
- 创建一个支持多时区的项目管理系统
- 实现自动检测用户时区的功能
- 编写时间本地化的单元测试
下一步学习计划
接下来我们将学习 Element Plus 无障碍设计实践与 ARIA 属性应用,深入了解如何让 Element Plus 应用更好地支持残障用户的使用需求。