工具完成
This commit is contained in:
550
src/components/tools/DateCalculator.vue
Normal file
550
src/components/tools/DateCalculator.vue
Normal file
@ -0,0 +1,550 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 模式切换 -->
|
||||
<div class="flex flex-wrap gap-4 justify-between items-center">
|
||||
<div class="flex items-center bg-card rounded-md p-1 border">
|
||||
<button
|
||||
:class="[
|
||||
'px-4 py-2 text-sm font-medium transition-all rounded-l-md',
|
||||
mode === 'diff' ? 'bg-primary text-white shadow-sm' : 'text-secondary hover:text-primary'
|
||||
]"
|
||||
@click="mode = 'diff'"
|
||||
>
|
||||
日期差值计算
|
||||
</button>
|
||||
<button
|
||||
:class="[
|
||||
'px-4 py-2 text-sm font-medium transition-all rounded-r-md',
|
||||
mode === 'add' ? 'bg-primary text-white shadow-sm' : 'text-secondary hover:text-primary'
|
||||
]"
|
||||
@click="mode = 'add'"
|
||||
>
|
||||
日期加减计算
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日期差值计算 -->
|
||||
<div v-if="mode === 'diff'" class="card p-6">
|
||||
<h2 class="text-lg font-medium text-primary mb-4">日期差值计算器</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- 输入部分 -->
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-secondary mb-2">开始日期</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="startDate"
|
||||
type="datetime-local"
|
||||
class="input-field flex-1"
|
||||
@input="calculateDateDiff"
|
||||
/>
|
||||
<button
|
||||
class="btn-secondary text-sm px-3 py-2"
|
||||
@click="setStartDateToCurrent"
|
||||
>
|
||||
当前时间
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<button
|
||||
class="btn-secondary text-sm px-4 py-2"
|
||||
@click="swapDates"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'calendar-alt']" class="mr-2" />
|
||||
交换日期
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-secondary mb-2">结束日期</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="endDate"
|
||||
type="datetime-local"
|
||||
class="input-field flex-1"
|
||||
@input="calculateDateDiff"
|
||||
/>
|
||||
<button
|
||||
class="btn-secondary text-sm px-3 py-2"
|
||||
@click="setEndDateToCurrent"
|
||||
>
|
||||
当前时间
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 结果部分 -->
|
||||
<div class="bg-block rounded-md p-4 border">
|
||||
<h3 class="text-primary font-medium mb-3">计算结果</h3>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="(result, key) in diffResults"
|
||||
:key="key"
|
||||
class="flex justify-between items-center py-2 border-b border-primary/10 last:border-0"
|
||||
>
|
||||
<span class="text-sm text-secondary">{{ getTimeUnitLabel(key) }}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-primary font-semibold">
|
||||
{{ result }} {{ getTimeUnitName(key) }}
|
||||
</span>
|
||||
<button
|
||||
@click="() => copyToClipboard(result.toString(), key)"
|
||||
class="text-tertiary hover:text-primary transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon :icon="copied === key ? ['fas', 'check'] : ['fas', 'copy']" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日期加减计算 -->
|
||||
<div v-if="mode === 'add'" class="card p-6">
|
||||
<h2 class="text-lg font-medium text-primary mb-4">日期加减计算器</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- 输入部分 -->
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-secondary mb-2">基准日期</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="baseDate"
|
||||
type="datetime-local"
|
||||
class="input-field flex-1"
|
||||
@input="calculateDateAddition"
|
||||
/>
|
||||
<button
|
||||
class="btn-secondary text-sm px-3 py-2"
|
||||
@click="setBaseDateToCurrent"
|
||||
>
|
||||
当前时间
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-secondary mb-2">操作类型</label>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
:class="[
|
||||
'flex-1 px-4 py-2 text-sm font-medium rounded transition-all',
|
||||
operation === 'add' ? 'bg-primary text-white' : 'bg-block text-secondary border'
|
||||
]"
|
||||
@click="() => setOperation('add')"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'plus']" class="mr-2" />
|
||||
加上
|
||||
</button>
|
||||
<button
|
||||
:class="[
|
||||
'flex-1 px-4 py-2 text-sm font-medium rounded transition-all',
|
||||
operation === 'subtract' ? 'bg-primary text-white' : 'bg-block text-secondary border'
|
||||
]"
|
||||
@click="() => setOperation('subtract')"
|
||||
>
|
||||
<FontAwesomeIcon :icon="['fas', 'minus']" class="mr-2" />
|
||||
减去
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-secondary mb-2">时间数量</label>
|
||||
<input
|
||||
v-model.number="timeAmount"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
class="input-field w-full"
|
||||
@input="calculateDateAddition"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-secondary mb-2">时间单位</label>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
<button
|
||||
v-for="unit in timeUnits"
|
||||
:key="unit.value"
|
||||
:class="[
|
||||
'px-3 py-2 text-xs rounded transition-all',
|
||||
timeUnit === unit.value ? 'bg-primary text-white' : 'btn-secondary'
|
||||
]"
|
||||
@click="() => setTimeUnit(unit.value)"
|
||||
>
|
||||
{{ unit.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 结果部分 -->
|
||||
<div class="bg-block rounded-md p-4 border">
|
||||
<h3 class="text-primary font-medium mb-3">计算结果</h3>
|
||||
|
||||
<div v-if="addResult" class="space-y-3">
|
||||
<div class="text-center">
|
||||
<div class="text-lg text-primary font-bold">{{ addResult }}</div>
|
||||
<button
|
||||
@click="() => copyToClipboard(addResult, 'result')"
|
||||
class="mt-2 btn-secondary text-sm"
|
||||
>
|
||||
<FontAwesomeIcon :icon="copied === 'result' ? ['fas', 'check'] : ['fas', 'copy']" class="mr-1" />
|
||||
{{ copied === 'result' ? '已复制' : '复制结果' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<div class="text-xs text-tertiary text-center">
|
||||
{{ formatCalculationDescription() }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-tertiary text-center py-4">
|
||||
请填写有效的基准日期和时间数量
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快速操作面板 -->
|
||||
<div class="card p-4">
|
||||
<h3 class="text-md font-medium text-primary mb-3">快速操作</h3>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||
<button
|
||||
v-for="quick in quickOperations"
|
||||
:key="quick.label"
|
||||
@click="() => applyQuickOperation(quick)"
|
||||
class="btn-secondary text-sm"
|
||||
>
|
||||
{{ quick.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
// 时间单位枚举
|
||||
enum TimeUnit {
|
||||
YEARS = 'years',
|
||||
MONTHS = 'months',
|
||||
WEEKS = 'weeks',
|
||||
DAYS = 'days',
|
||||
HOURS = 'hours',
|
||||
MINUTES = 'minutes'
|
||||
}
|
||||
|
||||
// 响应式状态
|
||||
const mode = ref<'diff' | 'add'>('diff')
|
||||
|
||||
// 日期差值计算状态
|
||||
const startDate = ref('')
|
||||
const endDate = ref('')
|
||||
const diffResults = ref<Record<string, number>>({})
|
||||
|
||||
// 日期加减计算状态
|
||||
const baseDate = ref('')
|
||||
const timeAmount = ref(1)
|
||||
const timeUnit = ref<TimeUnit>(TimeUnit.DAYS)
|
||||
const operation = ref<'add' | 'subtract'>('add')
|
||||
const addResult = ref('')
|
||||
|
||||
// 复制状态
|
||||
const copied = ref<string | null>(null)
|
||||
|
||||
// 时间单位选项
|
||||
const timeUnits = [
|
||||
{ value: TimeUnit.YEARS, label: '年' },
|
||||
{ value: TimeUnit.MONTHS, label: '月' },
|
||||
{ value: TimeUnit.WEEKS, label: '周' },
|
||||
{ value: TimeUnit.DAYS, label: '天' },
|
||||
{ value: TimeUnit.HOURS, label: '小时' },
|
||||
{ value: TimeUnit.MINUTES, label: '分钟' }
|
||||
]
|
||||
|
||||
// 快速操作选项
|
||||
const quickOperations = [
|
||||
{ label: '距今一周', action: () => setQuickDiff(-7, 'days') },
|
||||
{ label: '距今一月', action: () => setQuickDiff(-1, 'months') },
|
||||
{ label: '一周后', action: () => setQuickAdd(7, 'days') },
|
||||
{ label: '一月后', action: () => setQuickAdd(1, 'months') }
|
||||
]
|
||||
|
||||
// 格式化日期为显示格式
|
||||
const formatDateForDisplay = (date: Date): string => {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
// 格式化日期为输入框格式
|
||||
const formatDateForInput = (date: Date): string => {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day}T${hours}:${minutes}`
|
||||
}
|
||||
|
||||
// 获取时间单位标签
|
||||
const getTimeUnitLabel = (key: string): string => {
|
||||
const labels: Record<string, string> = {
|
||||
years: '相差年数',
|
||||
months: '相差月数',
|
||||
weeks: '相差周数',
|
||||
days: '相差天数',
|
||||
hours: '相差小时',
|
||||
minutes: '相差分钟',
|
||||
seconds: '相差秒数',
|
||||
milliseconds: '相差毫秒'
|
||||
}
|
||||
return labels[key] || key
|
||||
}
|
||||
|
||||
// 获取时间单位名称
|
||||
const getTimeUnitName = (key: string): string => {
|
||||
const names: Record<string, string> = {
|
||||
years: '年',
|
||||
months: '月',
|
||||
weeks: '周',
|
||||
days: '天',
|
||||
hours: '小时',
|
||||
minutes: '分钟',
|
||||
seconds: '秒',
|
||||
milliseconds: '毫秒'
|
||||
}
|
||||
return names[key] || ''
|
||||
}
|
||||
|
||||
// 计算日期差值
|
||||
const calculateDateDiff = () => {
|
||||
if (!startDate.value || !endDate.value) {
|
||||
diffResults.value = {}
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const startDateTime = new Date(startDate.value)
|
||||
const endDateTime = new Date(endDate.value)
|
||||
|
||||
if (isNaN(startDateTime.getTime()) || isNaN(endDateTime.getTime())) {
|
||||
return
|
||||
}
|
||||
|
||||
// 计算毫秒差值
|
||||
const diffMs = endDateTime.getTime() - startDateTime.getTime()
|
||||
|
||||
// 计算各个单位的差值
|
||||
const diffSeconds = Math.floor(diffMs / 1000)
|
||||
const diffMinutes = Math.floor(diffSeconds / 60)
|
||||
const diffHours = Math.floor(diffMinutes / 60)
|
||||
const diffDays = Math.floor(diffHours / 24)
|
||||
const diffWeeks = Math.floor(diffDays / 7)
|
||||
|
||||
// 计算月份差
|
||||
let months = (endDateTime.getFullYear() - startDateTime.getFullYear()) * 12
|
||||
months += endDateTime.getMonth() - startDateTime.getMonth()
|
||||
|
||||
// 计算年份差
|
||||
const diffYears = Math.floor(months / 12)
|
||||
|
||||
// 设置结果
|
||||
diffResults.value = {
|
||||
years: diffYears,
|
||||
months: months,
|
||||
weeks: diffWeeks,
|
||||
days: diffDays,
|
||||
hours: diffHours,
|
||||
minutes: diffMinutes,
|
||||
seconds: diffSeconds,
|
||||
milliseconds: diffMs
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('计算错误:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算日期加减
|
||||
const calculateDateAddition = () => {
|
||||
if (!baseDate.value || isNaN(timeAmount.value)) {
|
||||
addResult.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const baseDateTime = new Date(baseDate.value)
|
||||
|
||||
if (isNaN(baseDateTime.getTime())) {
|
||||
return
|
||||
}
|
||||
|
||||
const resultDate = new Date(baseDateTime)
|
||||
const sign = operation.value === 'add' ? 1 : -1
|
||||
|
||||
switch (timeUnit.value) {
|
||||
case TimeUnit.YEARS:
|
||||
resultDate.setFullYear(resultDate.getFullYear() + sign * timeAmount.value)
|
||||
break
|
||||
case TimeUnit.MONTHS:
|
||||
resultDate.setMonth(resultDate.getMonth() + sign * timeAmount.value)
|
||||
break
|
||||
case TimeUnit.WEEKS:
|
||||
resultDate.setDate(resultDate.getDate() + sign * timeAmount.value * 7)
|
||||
break
|
||||
case TimeUnit.DAYS:
|
||||
resultDate.setDate(resultDate.getDate() + sign * timeAmount.value)
|
||||
break
|
||||
case TimeUnit.HOURS:
|
||||
resultDate.setHours(resultDate.getHours() + sign * timeAmount.value)
|
||||
break
|
||||
case TimeUnit.MINUTES:
|
||||
resultDate.setMinutes(resultDate.getMinutes() + sign * timeAmount.value)
|
||||
break
|
||||
}
|
||||
|
||||
// 格式化结果
|
||||
addResult.value = formatDateForDisplay(resultDate)
|
||||
} catch (error) {
|
||||
console.error('计算错误:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化计算描述
|
||||
const formatCalculationDescription = (): string => {
|
||||
const unitName = timeUnits.find(unit => unit.value === timeUnit.value)?.label || ''
|
||||
const op = operation.value === 'add' ? '加上' : '减去'
|
||||
return `${formatDateForDisplay(new Date(baseDate.value))} ${op} ${timeAmount.value} ${unitName}`
|
||||
}
|
||||
|
||||
// 设置开始日期为当前时间
|
||||
const setStartDateToCurrent = () => {
|
||||
const now = formatDateForInput(new Date())
|
||||
startDate.value = now
|
||||
calculateDateDiff()
|
||||
}
|
||||
|
||||
// 设置结束日期为当前时间
|
||||
const setEndDateToCurrent = () => {
|
||||
const now = formatDateForInput(new Date())
|
||||
endDate.value = now
|
||||
calculateDateDiff()
|
||||
}
|
||||
|
||||
// 设置基准日期为当前时间
|
||||
const setBaseDateToCurrent = () => {
|
||||
const now = formatDateForInput(new Date())
|
||||
baseDate.value = now
|
||||
calculateDateAddition()
|
||||
}
|
||||
|
||||
// 交换开始和结束日期
|
||||
const swapDates = () => {
|
||||
const temp = startDate.value
|
||||
startDate.value = endDate.value
|
||||
endDate.value = temp
|
||||
calculateDateDiff()
|
||||
}
|
||||
|
||||
// 设置时间单位
|
||||
const setTimeUnit = (unit: TimeUnit) => {
|
||||
timeUnit.value = unit
|
||||
calculateDateAddition()
|
||||
}
|
||||
|
||||
// 设置操作类型
|
||||
const setOperation = (op: 'add' | 'subtract') => {
|
||||
operation.value = op
|
||||
calculateDateAddition()
|
||||
}
|
||||
|
||||
// 复制到剪贴板
|
||||
const copyToClipboard = async (text: string, type: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
copied.value = type
|
||||
setTimeout(() => {
|
||||
copied.value = null
|
||||
}, 2000)
|
||||
} catch (error) {
|
||||
console.error('复制失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置快速日期差值
|
||||
const setQuickDiff = (amount: number, unit: string) => {
|
||||
const now = new Date()
|
||||
const past = new Date(now)
|
||||
|
||||
switch (unit) {
|
||||
case 'days':
|
||||
past.setDate(past.getDate() + amount)
|
||||
break
|
||||
case 'months':
|
||||
past.setMonth(past.getMonth() + amount)
|
||||
break
|
||||
}
|
||||
|
||||
startDate.value = formatDateForInput(past)
|
||||
endDate.value = formatDateForInput(now)
|
||||
mode.value = 'diff'
|
||||
calculateDateDiff()
|
||||
}
|
||||
|
||||
// 设置快速日期加减
|
||||
const setQuickAdd = (amount: number, unit: string) => {
|
||||
const now = new Date()
|
||||
baseDate.value = formatDateForInput(now)
|
||||
timeAmount.value = amount
|
||||
|
||||
switch (unit) {
|
||||
case 'days':
|
||||
timeUnit.value = TimeUnit.DAYS
|
||||
break
|
||||
case 'months':
|
||||
timeUnit.value = TimeUnit.MONTHS
|
||||
break
|
||||
}
|
||||
|
||||
operation.value = 'add'
|
||||
mode.value = 'add'
|
||||
calculateDateAddition()
|
||||
}
|
||||
|
||||
// 应用快速操作
|
||||
const applyQuickOperation = (quick: any) => {
|
||||
quick.action()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
const now = new Date()
|
||||
const oneWeekAgo = new Date(now)
|
||||
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7)
|
||||
|
||||
startDate.value = formatDateForInput(oneWeekAgo)
|
||||
endDate.value = formatDateForInput(now)
|
||||
baseDate.value = formatDateForInput(now)
|
||||
|
||||
// 初始化时计算一次
|
||||
calculateDateDiff()
|
||||
calculateDateAddition()
|
||||
})
|
||||
</script>
|
Reference in New Issue
Block a user