feat: 实现微博签到小程序功能
- 实现签到主页面,包含签到按钮、连续天数、今日状态展示 - 实现签到记录页面,包含日历视图和签到历史列表 - 实现个人中心页面,包含用户信息和签到统计 - 后端实现签到、查询状态、查询历史三个接口 - 使用 Supabase 存储签到记录数据 - 采用星空主题设计,深蓝紫渐变背景 + 金色星光强调色 - 完成所有接口测试和前后端匹配验证 - 通过 ESLint 检查和编译验证
This commit is contained in:
170
src/pages/record/index.tsx
Normal file
170
src/pages/record/index.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
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
|
||||
user_id: string
|
||||
sign_date: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 签到记录页面
|
||||
*/
|
||||
const RecordPage: FC = () => {
|
||||
const [records, setRecords] = useState<SignInRecord[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
// 页面显示时获取签到记录
|
||||
useDidShow(async () => {
|
||||
await fetchRecords()
|
||||
})
|
||||
|
||||
// 获取签到记录
|
||||
const fetchRecords = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const res = await Network.request({
|
||||
url: '/api/signin/history',
|
||||
method: 'GET'
|
||||
})
|
||||
console.log('签到记录:', res.data)
|
||||
if (res.data?.code === 200 && res.data?.data) {
|
||||
setRecords(res.data.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取签到记录失败:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日期显示
|
||||
const formatDate = (dateStr: string) => {
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = date.getMonth() + 1
|
||||
const day = date.getDate()
|
||||
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
const weekDay = weekDays[date.getDay()]
|
||||
return `${year}年${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; isSigned: boolean }[] = []
|
||||
|
||||
// 填充前面的空白
|
||||
for (let i = 0; i < firstDay.getDay(); i++) {
|
||||
days.push({ date: null as any, isSigned: false })
|
||||
}
|
||||
|
||||
// 填充日期
|
||||
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 isSigned = records.some(r => r.sign_date === dateStr)
|
||||
days.push({ date, isSigned })
|
||||
}
|
||||
|
||||
return days
|
||||
}
|
||||
|
||||
const monthDays = getCurrentMonthDays()
|
||||
const now = new Date()
|
||||
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
|
||||
|
||||
return (
|
||||
<View className="min-h-screen bg-gray-900 px-4 py-6">
|
||||
{/* 月份标题 */}
|
||||
<View className="mb-6">
|
||||
<Text className="block text-white text-xl font-bold">{monthNames[now.getMonth()]} {now.getFullYear()}</Text>
|
||||
</View>
|
||||
|
||||
{/* 日历视图 */}
|
||||
<View className="bg-gray-800 rounded-2xl p-4 mb-6">
|
||||
{/* 星期标题 */}
|
||||
<View className="flex flex-row mb-3">
|
||||
{['日', '一', '二', '三', '四', '五', '六'].map((day, index) => (
|
||||
<View key={index} className="flex-1 flex justify-center">
|
||||
<Text className="text-gray-400 text-sm">{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-8 h-8 rounded-full flex items-center justify-center ${
|
||||
item.isSigned
|
||||
? 'bg-blue-500'
|
||||
: item.date.toDateString() === now.toDateString()
|
||||
? 'bg-gray-700 border border-blue-400'
|
||||
: 'bg-transparent'
|
||||
}`}
|
||||
>
|
||||
<Text
|
||||
className={`text-sm ${
|
||||
item.isSigned
|
||||
? 'text-white'
|
||||
: item.date.toDateString() === now.toDateString()
|
||||
? 'text-blue-400'
|
||||
: 'text-gray-400'
|
||||
}`}
|
||||
>
|
||||
{item.date.getDate()}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View />
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 签到记录列表 */}
|
||||
<View className="bg-gray-800 rounded-2xl p-4">
|
||||
<Text className="block text-white text-lg font-bold mb-4">签到记录</Text>
|
||||
|
||||
{loading ? (
|
||||
<View className="flex justify-center py-8">
|
||||
<Text className="text-gray-500">加载中...</Text>
|
||||
</View>
|
||||
) : records.length === 0 ? (
|
||||
<View className="flex flex-col items-center justify-center py-8">
|
||||
<Text className="text-gray-500 text-base">暂无签到记录</Text>
|
||||
<Text className="text-gray-600 text-sm mt-2">开始你的第一次签到吧</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className="flex flex-col gap-3">
|
||||
{records.map((record) => (
|
||||
<View key={record.id} className="flex flex-row items-center gap-3 py-3 border-b border-gray-700 last:border-b-0">
|
||||
<View className="w-2 h-2 rounded-full bg-blue-500" />
|
||||
<View className="flex-1">
|
||||
<Text className="block text-white text-sm">{formatDate(record.sign_date)}</Text>
|
||||
</View>
|
||||
<View className="bg-blue-500 rounded-full px-3 py-1">
|
||||
<Text className="text-white text-xs">已签到</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default RecordPage
|
||||
Reference in New Issue
Block a user