Files

265 lines
8.4 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<el-card class="sign-in">
<!-- 标题和操作区 -->
<div class="sign-in__header flex justify-between items-center mb-2">
<h3 class="text-base font-semibold text-gray-800">{{ t('member.sign.page_title') }}</h3>
<div class="sign-in__actions flex gap-2">
<el-button type="text" @click="showRules" size="small">{{ t('member.sign.sign_rules') }}</el-button>
<el-button
type="primary"
:disabled="isSignedToday || isLoading"
@click="signInAction"
size="small"
>
<el-icon v-if="isLoading"><Loading /></el-icon>
{{ isSignedToday ? t('member.sign.signed') : t('member.sign.sign_button') }}
</el-button>
</div>
</div>
<!-- 签到提示 -->
<div v-if="showTip" class="sign-in__tip bg-green-50 border border-green-200 rounded-md p-2 flex justify-between items-center mb-3">
<span class="text-green-700 text-sm">{{ t('member.sign.tomorrow_points', { points: tomorrowPoints }) }}</span>
<el-button type="text" size="small" @click="closeTip">×</el-button>
</div>
<!-- 分隔线 -->
<div class="sign-in__divider border-t border-gray-200 my-3"></div>
<!-- 日历导航 -->
<div class="sign-in__calendar-header flex justify-between items-center mb-2">
<div class="flex items-center gap-2">
<el-button type="text" @click="prevMonth" size="small">‹</el-button>
<span class="text-gray-700 text-sm">{{ currentYear }}年{{ currentMonth }}月</span>
<el-button type="text" @click="nextMonth" size="small">›</el-button>
</div>
<span class="sign-in__consecutive text-primary text-sm">{{ t('member.sign.consecutive_days') }} {{ consecutiveDays }} {{ t('member.sign.days') }}</span>
</div>
<!-- 日历 -->
<div class="sign-in__calendar">
<!-- 星期标题和日期表格 -->
<table class="w-full border-collapse">
<thead>
<tr>
<th v-for="day in weekdays" :key="day" class="w-10 h-8 text-center text-gray-500 text-xs">
{{ day }}
</th>
</tr>
</thead>
<tbody>
<!-- 日期行 -->
<tr v-for="(week, index) in calendarWeeks" :key="index">
<td v-for="(date, dayIndex) in week" :key="dayIndex" class="h-14 text-center">
<div
v-if="date"
class="w-10 h-10 mx-auto flex items-center justify-center text-sm rounded-full cursor-pointer transition-colors duration-200"
:class="{
'bg-[#67c23a] text-white font-medium': isSigned(date),
'bg-blue-50 text-blue-600': isToday(date) && !isSigned(date),
'text-gray-800': !isToday(date) && !isSigned(date)
}"
@mouseenter="!isSigned(date) && $event.target.classList.add('bg-blue-50', 'text-blue-600')"
@mouseleave="!isSigned(date) && $event.target.classList.remove('bg-blue-50', 'text-blue-600')"
>
{{ date.getDate() }}
</div>
<div v-else class="w-10 h-10 mx-auto"></div>
</td>
</tr>
</tbody>
</table>
</div>
</el-card>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useMemberStore } from '~/stores/member'
import { ElMessage } from 'element-plus'
import { Loading } from '@element-plus/icons-vue'
import { signIn, getSignStatus, getSignCalendar } from '~/api/member'
const memberStore = useMemberStore()
// 响应式数据
const currentYear = ref(new Date().getFullYear())
const currentMonth = ref(new Date().getMonth() + 1)
const isSignedToday = ref(false)
const consecutiveDays = ref(0)
const showTip = ref(true)
const isLoading = ref(false)
const signedDates = ref<Set<string>>(new Set())
// 计算属性
const weekdays = ['一', '二', '三', '四', '五', '六', '日'] // 以星期一为起始
const tomorrowPoints = computed(() => {
// 根据连续签到天数计算明天可获得的积分
if (consecutiveDays.value >= 7) return 5
if (consecutiveDays.value >= 3) return 3
return 2
})
// 生成日历数据(按周分组)
const calendarWeeks = computed(() => {
const weeks = []
const firstDay = new Date(currentYear.value, currentMonth.value - 1, 1)
const lastDay = new Date(currentYear.value, currentMonth.value, 0)
// 计算当月第一天是星期几(1-7,1=星期一)
let firstDayWeekday = firstDay.getDay()
firstDayWeekday = firstDayWeekday === 0 ? 7 : firstDayWeekday
// 生成日期数组
let week: (Date | null)[] = []
// 填充月初空白
for (let i = 0; i < firstDayWeekday - 1; i++) {
week.push(null)
}
// 填充当月日期
for (let day = 1; day <= lastDay.getDate(); day++) {
// 确保创建日期时设置为当天的开始时间
const currentDate = new Date(currentYear.value, currentMonth.value - 1, day)
currentDate.setHours(0, 0, 0, 0)
week.push(currentDate)
// 每周结束,开始新周
if (week.length === 7) {
weeks.push([...week])
week = []
}
}
// 填充月末空白
while (week.length > 0 && week.length < 7) {
week.push(null)
}
// 添加最后一周
if (week.length === 7) {
weeks.push(week)
}
return weeks
})
// 检查日期是否是今天
const isToday = (date: Date) => {
// 使用固定的日期格式化方法,确保不受时区影响
const today = new Date()
const year = today.getFullYear()
const month = String(today.getMonth() + 1).padStart(2, '0')
const day = String(today.getDate()).padStart(2, '0')
const todayStr = `${year}-${month}-${day}`
const checkYear = date.getFullYear()
const checkMonth = String(date.getMonth() + 1).padStart(2, '0')
const checkDay = String(date.getDate()).padStart(2, '0')
const checkDateStr = `${checkYear}-${checkMonth}-${checkDay}`
console.log('Checking if today:', {
today: todayStr,
checkDate: checkDateStr,
isEqual: todayStr === checkDateStr
})
return todayStr === checkDateStr
}
// 检查日期是否已签到
const isSigned = (date: Date) => {
// 使用固定的日期格式化方法,确保不受时区影响
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const dateStr = `${year}-${month}-${day}`
console.log('Checking if signed:', {
date: dateStr,
isSigned: signedDates.value.has(dateStr),
signedDates: Array.from(signedDates.value)
})
return signedDates.value.has(dateStr)
}
// API 调用
const fetchSignStatus = async () => {
try {
const data = await getSignStatus() as any
isSignedToday.value = data.is_signed_today
consecutiveDays.value = data.continuous_days
} catch (error: any) {
console.error('获取签到状态失败:', error)
ElMessage.error(error.message || t('member.sign.get_sign_status_failed'))
}
}
const fetchSignCalendar = async () => {
try {
const result = await getSignCalendar(currentYear.value, currentMonth.value) as any
signedDates.value.clear()
result.calendar.forEach((date: string) => {
signedDates.value.add(date)
})
} catch (error: any) {
console.error('获取签到日历失败:', error)
ElMessage.error(error.message || t('member.sign.get_sign_calendar_failed'))
}
}
// 方法
const showRules = () => {
// 显示签到规则
ElMessage.info(t('member.sign.rules_content'))
}
const signInAction = async () => {
try {
isLoading.value = true
const data = await signIn()
isSignedToday.value = true
consecutiveDays.value = data.continuous_days
ElMessage.success(t('member.sign.sign_success', { points: data.points }))
// 更新签到日历
await fetchSignCalendar()
} catch (error: any) {
console.error('签到失败:', error)
ElMessage.error(error.message || t('member.sign.sign_failed'))
} finally {
isLoading.value = false
}
}
const closeTip = () => {
showTip.value = false
}
const prevMonth = async () => {
if (currentMonth.value === 1) {
currentMonth.value = 12
currentYear.value--
} else {
currentMonth.value--
}
await fetchSignCalendar()
}
const nextMonth = async () => {
if (currentMonth.value === 12) {
currentMonth.value = 1
currentYear.value++
} else {
currentMonth.value++
}
await fetchSignCalendar()
}
// 生命周期
onMounted(async () => {
// 初始化签到状态
await fetchSignStatus()
await fetchSignCalendar()
})
</script>