feat: 适配5.x后端,重构前端架构

主要变更:
1. 重构API目录结构,采用模块化设计,适配5.x后端接口规范
2. 优化布局组件,提升用户界面体验
3. 增强国际化支持,完善多语言配置
4. 新增权限控制功能,支持前端路由权限管理
5. 优化认证流程,支持JWT认证机制
6. 新增中间件和插件,提升系统扩展性
7. 优化状态管理,采用更清晰的模块化设计
8. 新增搜索框、广告等组件,丰富页面功能
9. 更改包管理工具从npm到pnpm,提升依赖管理效率
10. 完善配置系统,支持多环境配置

适配5.x后端特性:
- 采用新的API接口规范
- 支持JWT认证机制
- 适配后端权限控制体系
- 支持后端国际化接口
- 优化数据交互方式
This commit is contained in:
Mr.April
2026-04-07 11:01:11 +08:00
parent 1b83f1890b
commit d84d972e73
127 changed files with 103052 additions and 14661 deletions
+264
View File
@@ -0,0 +1,264 @@
<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>