- 实现超话列表页面,显示关注超话和签到状态 - 实现一键批量签到功能,可一次性签到所有未签到超话 - 实现签到记录页面,包含日历视图和历史记录 - 实现个人中心页面,显示用户信息和签到统计 - 后端实现超话列表、签到、记录查询、用户信息四个接口 - 使用 Supabase 存储签到记录数据 - 采用微博风格设计,橙色主题 + 白色背景 - 完成所有接口测试和前后端匹配验证 - 通过 ESLint 检查和编译验证
193 lines
6.5 KiB
TypeScript
193 lines
6.5 KiB
TypeScript
import { View, Text } from '@tarojs/components'
|
|
import { useDidShow } from '@tarojs/taro'
|
|
import { FC, useState } from 'react'
|
|
import { Network } from '@/network'
|
|
import './index.css'
|
|
|
|
interface SignInRecord {
|
|
id: number
|
|
date: string
|
|
count: number
|
|
details: string[]
|
|
}
|
|
|
|
/**
|
|
* 签到记录页面
|
|
*/
|
|
const RecordPage: FC = () => {
|
|
const [records, setRecords] = useState<SignInRecord[]>([])
|
|
const [stats, setStats] = useState({
|
|
continuousDays: 0,
|
|
totalDays: 0
|
|
})
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
// 页面显示时获取记录
|
|
useDidShow(async () => {
|
|
await fetchRecords()
|
|
})
|
|
|
|
// 获取签到记录
|
|
const fetchRecords = async () => {
|
|
try {
|
|
setLoading(true)
|
|
const res = await Network.request({
|
|
url: '/api/super-topics/records',
|
|
method: 'GET'
|
|
})
|
|
console.log('签到记录:', res.data)
|
|
if (res.data?.code === 200 && res.data?.data) {
|
|
setRecords(res.data.data.records || [])
|
|
setStats({
|
|
continuousDays: res.data.data.continuousDays || 0,
|
|
totalDays: res.data.data.totalDays || 0
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('获取签到记录失败:', error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
// 格式化日期
|
|
const formatDate = (dateStr: string) => {
|
|
const date = new Date(dateStr)
|
|
const month = date.getMonth() + 1
|
|
const day = date.getDate()
|
|
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
|
const weekDay = weekDays[date.getDay()]
|
|
return `${month}月${day}日 ${weekDay}`
|
|
}
|
|
|
|
// 获取当前月份的天数
|
|
const getCurrentMonthDays = () => {
|
|
const now = new Date()
|
|
const year = now.getFullYear()
|
|
const month = now.getMonth()
|
|
const firstDay = new Date(year, month, 1)
|
|
const lastDay = new Date(year, month + 1, 0)
|
|
const days: { date: Date | null; hasRecord: boolean }[] = []
|
|
|
|
// 填充前面的空白
|
|
for (let i = 0; i < firstDay.getDay(); i++) {
|
|
days.push({ date: null, hasRecord: false })
|
|
}
|
|
|
|
// 获取有签到记录的日期
|
|
const recordDates = records.map(r => r.date)
|
|
|
|
// 填充日期
|
|
for (let i = 1; i <= lastDay.getDate(); i++) {
|
|
const date = new Date(year, month, i)
|
|
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(i).padStart(2, '0')}`
|
|
const hasRecord = recordDates.includes(dateStr)
|
|
days.push({ date, hasRecord })
|
|
}
|
|
|
|
return days
|
|
}
|
|
|
|
const monthDays = getCurrentMonthDays()
|
|
const now = new Date()
|
|
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
|
|
|
|
return (
|
|
<View className="min-h-screen bg-gray-50 px-4 py-4">
|
|
{/* 统计卡片 */}
|
|
<View className="flex flex-row gap-3 mb-4">
|
|
<View className="flex-1 bg-orange-50 rounded-lg p-4">
|
|
<Text className="block text-gray-600 text-xs">连续签到</Text>
|
|
<Text className="block text-orange-600 text-3xl font-bold mt-1">{stats.continuousDays}</Text>
|
|
<Text className="block text-gray-400 text-xs mt-1">天</Text>
|
|
</View>
|
|
<View className="flex-1 bg-white rounded-lg p-4 border border-gray-100">
|
|
<Text className="block text-gray-600 text-xs">累计签到</Text>
|
|
<Text className="block text-gray-900 text-3xl font-bold mt-1">{stats.totalDays}</Text>
|
|
<Text className="block text-gray-400 text-xs mt-1">天</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 日历视图 */}
|
|
<View className="bg-white rounded-lg p-4 mb-4 border border-gray-100">
|
|
<Text className="block text-gray-900 font-semibold mb-3">
|
|
{monthNames[now.getMonth()]} {now.getFullYear()}
|
|
</Text>
|
|
|
|
{/* 星期标题 */}
|
|
<View className="flex flex-row mb-2">
|
|
{['日', '一', '二', '三', '四', '五', '六'].map((day, index) => (
|
|
<View key={index} className="flex-1 flex justify-center">
|
|
<Text className="text-gray-400 text-xs">{day}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
|
|
{/* 日期网格 */}
|
|
<View className="flex flex-row flex-wrap">
|
|
{monthDays.map((item, index) => (
|
|
<View key={index} className="w-[14.28%] aspect-square flex justify-center items-center mb-1">
|
|
{item.date ? (
|
|
<View
|
|
className={`w-6 h-6 rounded-full flex items-center justify-center ${
|
|
item.hasRecord
|
|
? 'bg-orange-500'
|
|
: item.date.toDateString() === now.toDateString()
|
|
? 'bg-orange-100'
|
|
: 'bg-transparent'
|
|
}`}
|
|
>
|
|
<Text
|
|
className={`text-xs ${
|
|
item.hasRecord
|
|
? 'text-white'
|
|
: item.date.toDateString() === now.toDateString()
|
|
? 'text-orange-600'
|
|
: 'text-gray-400'
|
|
}`}
|
|
>
|
|
{item.date.getDate()}
|
|
</Text>
|
|
</View>
|
|
) : (
|
|
<View />
|
|
)}
|
|
</View>
|
|
))}
|
|
</View>
|
|
</View>
|
|
|
|
{/* 签到记录列表 */}
|
|
<View className="bg-white rounded-lg p-4 border border-gray-100">
|
|
<Text className="block text-gray-900 font-semibold mb-3">签到历史</Text>
|
|
|
|
{loading ? (
|
|
<View className="flex justify-center py-8">
|
|
<Text className="text-gray-400">加载中...</Text>
|
|
</View>
|
|
) : records.length === 0 ? (
|
|
<View className="flex flex-col items-center justify-center py-8">
|
|
<Text className="text-gray-400 text-sm">暂无签到记录</Text>
|
|
</View>
|
|
) : (
|
|
<View className="flex flex-col gap-2">
|
|
{records.slice(0, 10).map((record) => (
|
|
<View key={record.id} className="flex flex-row items-center gap-3 py-2 border-b border-gray-50 last:border-b-0">
|
|
<View className="w-1.5 h-1.5 rounded-full bg-orange-500" />
|
|
<View className="flex-1">
|
|
<Text className="block text-gray-700 text-sm">{formatDate(record.date)}</Text>
|
|
</View>
|
|
<View className="bg-orange-50 rounded-full px-2 py-0.5">
|
|
<Text className="text-orange-600 text-xs">{record.count} 个超话</Text>
|
|
</View>
|
|
</View>
|
|
))}
|
|
</View>
|
|
)}
|
|
</View>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export default RecordPage
|