feat: 实现微博超话签到小程序功能
- 实现超话列表页面,显示关注超话和签到状态 - 实现一键批量签到功能,可一次性签到所有未签到超话 - 实现签到记录页面,包含日历视图和历史记录 - 实现个人中心页面,显示用户信息和签到统计 - 后端实现超话列表、签到、记录查询、用户信息四个接口 - 使用 Supabase 存储签到记录数据 - 采用微博风格设计,橙色主题 + 白色背景 - 完成所有接口测试和前后端匹配验证 - 通过 ESLint 检查和编译验证
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
export default typeof definePageConfig === 'function'
|
||||
? definePageConfig({
|
||||
navigationBarTitleText: '微博签到',
|
||||
navigationBarBackgroundColor: '#111827',
|
||||
navigationBarTextStyle: 'white'
|
||||
navigationBarTitleText: '微博超话签到',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
enablePullDownRefresh: true
|
||||
})
|
||||
: {
|
||||
navigationBarTitleText: '微博签到',
|
||||
navigationBarBackgroundColor: '#111827',
|
||||
navigationBarTextStyle: 'white'
|
||||
navigationBarTitleText: '微博超话签到',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
enablePullDownRefresh: true
|
||||
}
|
||||
|
||||
@@ -1,120 +1,211 @@
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import { useDidShow } from '@tarojs/taro'
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import { useDidShow, usePullDownRefresh } from '@tarojs/taro'
|
||||
import { FC, useState } from 'react'
|
||||
import { Network } from '@/network'
|
||||
import './index.css'
|
||||
|
||||
interface SignInStatus {
|
||||
todaySignedIn: boolean
|
||||
continuousDays: number
|
||||
totalDays: number
|
||||
interface SuperTopic {
|
||||
id: string
|
||||
name: string
|
||||
cover: string
|
||||
memberCount: number
|
||||
isSignedIn: boolean
|
||||
signPoints?: number
|
||||
}
|
||||
|
||||
interface SigninStatus {
|
||||
total: number
|
||||
signedIn: number
|
||||
notSignedIn: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 签到主页面
|
||||
* 超话列表页面
|
||||
*/
|
||||
const IndexPage: FC = () => {
|
||||
const [signInStatus, setSignInStatus] = useState<SignInStatus>({
|
||||
todaySignedIn: false,
|
||||
continuousDays: 0,
|
||||
totalDays: 0
|
||||
const [topics, setTopics] = useState<SuperTopic[]>([])
|
||||
const [status, setStatus] = useState<SigninStatus>({
|
||||
total: 0,
|
||||
signedIn: 0,
|
||||
notSignedIn: 0
|
||||
})
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [signingIn, setSigningIn] = useState(false)
|
||||
|
||||
// 页面显示时获取签到状态
|
||||
// 页面显示时获取超话列表
|
||||
useDidShow(async () => {
|
||||
await fetchSignInStatus()
|
||||
await fetchTopics()
|
||||
})
|
||||
|
||||
// 获取签到状态
|
||||
const fetchSignInStatus = async () => {
|
||||
// 下拉刷新
|
||||
usePullDownRefresh(async () => {
|
||||
await fetchTopics()
|
||||
})
|
||||
|
||||
// 获取超话列表
|
||||
const fetchTopics = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const res = await Network.request({
|
||||
url: '/api/signin/status',
|
||||
url: '/api/super-topics',
|
||||
method: 'GET'
|
||||
})
|
||||
console.log('签到状态:', res.data)
|
||||
console.log('超话列表:', res.data)
|
||||
if (res.data?.code === 200 && res.data?.data) {
|
||||
setSignInStatus(res.data.data)
|
||||
const topicList = res.data.data.topics || []
|
||||
setTopics(topicList)
|
||||
setStatus({
|
||||
total: topicList.length,
|
||||
signedIn: topicList.filter((t: SuperTopic) => t.isSignedIn).length,
|
||||
notSignedIn: topicList.filter((t: SuperTopic) => !t.isSignedIn).length
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取签到状态失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 签到
|
||||
const handleSignIn = async () => {
|
||||
if (loading || signInStatus.todaySignedIn) return
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await Network.request({
|
||||
url: '/api/signin',
|
||||
method: 'POST'
|
||||
})
|
||||
console.log('签到结果:', res.data)
|
||||
|
||||
if (res.data?.code === 200) {
|
||||
// 更新签到状态
|
||||
await fetchSignInStatus()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('签到失败:', error)
|
||||
console.error('获取超话列表失败:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 一键签到所有超话
|
||||
const handleSignAll = async () => {
|
||||
if (signingIn) return
|
||||
|
||||
setSigningIn(true)
|
||||
const unsignedTopics = topics.filter(t => !t.isSignedIn)
|
||||
|
||||
if (unsignedTopics.length === 0) {
|
||||
setSigningIn(false)
|
||||
return
|
||||
}
|
||||
|
||||
let successCount = 0
|
||||
|
||||
for (const topic of unsignedTopics) {
|
||||
try {
|
||||
const res = await Network.request({
|
||||
url: '/api/super-topics/signin',
|
||||
method: 'POST',
|
||||
data: { topicId: topic.id }
|
||||
})
|
||||
if (res.data?.code === 200) {
|
||||
successCount++
|
||||
// 更新该超话的状态
|
||||
setTopics(prev => prev.map(t =>
|
||||
t.id === topic.id ? { ...t, isSignedIn: true } : t
|
||||
))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`签到失败 [${topic.name}]:`, error)
|
||||
}
|
||||
// 添加延迟避免请求过快
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
}
|
||||
|
||||
// 更新统计
|
||||
setStatus(prev => ({
|
||||
...prev,
|
||||
signedIn: prev.signedIn + successCount,
|
||||
notSignedIn: prev.notSignedIn - successCount
|
||||
}))
|
||||
|
||||
setSigningIn(false)
|
||||
}
|
||||
|
||||
// 格式化成员数量
|
||||
const formatMemberCount = (count: number) => {
|
||||
if (count >= 10000) {
|
||||
return `${(count / 10000).toFixed(1)}万`
|
||||
}
|
||||
return count.toString()
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="min-h-screen bg-gradient-to-b from-gray-900 via-blue-900 to-gray-900 px-4 py-6">
|
||||
{/* 顶部标题 */}
|
||||
<View className="text-center mb-8">
|
||||
<Text className="block text-white text-3xl font-bold">微博签到</Text>
|
||||
<Text className="block text-blue-300 text-sm mt-2">每天签到,点亮星空</Text>
|
||||
</View>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<View className="flex flex-row gap-3 mb-8">
|
||||
<View className="flex-1 bg-gradient-to-br from-blue-600 to-purple-600 rounded-2xl p-4">
|
||||
<Text className="block text-blue-200 text-xs">连续签到</Text>
|
||||
<Text className="block text-white text-3xl font-bold mt-1">{signInStatus.continuousDays}</Text>
|
||||
<Text className="block text-blue-200 text-xs mt-1">天</Text>
|
||||
<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-3">
|
||||
<Text className="block text-gray-600 text-xs">已关注</Text>
|
||||
<Text className="block text-orange-600 text-2xl font-bold">{status.total}</Text>
|
||||
</View>
|
||||
<View className="flex-1 bg-gray-800 rounded-2xl p-4">
|
||||
<Text className="block text-gray-400 text-xs">累计签到</Text>
|
||||
<Text className="block text-white text-3xl font-bold mt-1">{signInStatus.totalDays}</Text>
|
||||
<Text className="block text-gray-400 text-xs mt-1">天</Text>
|
||||
<View className="flex-1 bg-green-50 rounded-lg p-3">
|
||||
<Text className="block text-gray-600 text-xs">已签到</Text>
|
||||
<Text className="block text-green-600 text-2xl font-bold">{status.signedIn}</Text>
|
||||
</View>
|
||||
<View className="flex-1 bg-gray-100 rounded-lg p-3">
|
||||
<Text className="block text-gray-600 text-xs">未签到</Text>
|
||||
<Text className="block text-gray-600 text-2xl font-bold">{status.notSignedIn}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 签到按钮 */}
|
||||
<View className="flex justify-center items-center mb-8">
|
||||
<View
|
||||
className={`w-40 h-40 rounded-full flex items-center justify-center shadow-lg ${
|
||||
signInStatus.todaySignedIn
|
||||
? 'bg-gradient-to-br from-green-500 to-green-600 shadow-green-500/50'
|
||||
: 'bg-gradient-to-br from-blue-500 to-purple-600 shadow-blue-500/50'
|
||||
} ${loading ? 'opacity-50' : ''}`}
|
||||
onClick={handleSignIn}
|
||||
>
|
||||
{loading ? (
|
||||
<Text className="text-white text-xl font-bold">签到中...</Text>
|
||||
) : signInStatus.todaySignedIn ? (
|
||||
<Text className="text-white text-xl font-bold">已签到</Text>
|
||||
) : (
|
||||
<Text className="text-white text-2xl font-bold">签到</Text>
|
||||
)}
|
||||
{/* 一键签到按钮 */}
|
||||
{status.notSignedIn > 0 && (
|
||||
<View className="mb-4">
|
||||
<View
|
||||
className={`rounded-full py-3 flex items-center justify-center ${
|
||||
signingIn ? 'bg-orange-300' : 'bg-orange-500'
|
||||
}`}
|
||||
onClick={handleSignAll}
|
||||
>
|
||||
<Text className="text-white text-base font-semibold">
|
||||
{signingIn ? '签到中...' : '一键签到全部超话'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 提示文字 */}
|
||||
<View className="text-center">
|
||||
{signInStatus.todaySignedIn ? (
|
||||
<Text className="block text-green-400 text-sm">今日已签到,明天继续加油!</Text>
|
||||
) : (
|
||||
<Text className="block text-yellow-400 text-sm">点击签到按钮,开启今日签到</Text>
|
||||
)}
|
||||
</View>
|
||||
{/* 超话列表 */}
|
||||
{loading ? (
|
||||
<View className="flex justify-center py-8">
|
||||
<Text className="text-gray-400">加载中...</Text>
|
||||
</View>
|
||||
) : topics.length === 0 ? (
|
||||
<View className="flex flex-col items-center justify-center py-20">
|
||||
<Text className="text-gray-400 text-base">暂无关注的超话</Text>
|
||||
<Text className="text-gray-300 text-sm mt-2">去微博关注一些超话吧</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className="flex flex-col gap-3">
|
||||
{topics.map((topic) => (
|
||||
<View
|
||||
key={topic.id}
|
||||
className="bg-white rounded-lg p-4 shadow-sm border border-gray-100"
|
||||
>
|
||||
<View className="flex flex-row items-center">
|
||||
{/* 超话封面 */}
|
||||
<Image
|
||||
className="w-12 h-12 rounded-lg bg-gray-200"
|
||||
src={topic.cover}
|
||||
mode="aspectFill"
|
||||
/>
|
||||
{/* 超话信息 */}
|
||||
<View className="flex-1 ml-3">
|
||||
<Text className="block text-gray-900 font-semibold text-base">
|
||||
{topic.name}
|
||||
</Text>
|
||||
<Text className="block text-gray-400 text-xs mt-1">
|
||||
{formatMemberCount(topic.memberCount)} 成员
|
||||
</Text>
|
||||
</View>
|
||||
{/* 签到状态 */}
|
||||
<View
|
||||
className={`px-3 py-1 rounded-full ${
|
||||
topic.isSignedIn ? 'bg-green-100' : 'bg-orange-100'
|
||||
}`}
|
||||
>
|
||||
<Text
|
||||
className={`text-xs ${
|
||||
topic.isSignedIn ? 'text-green-600' : 'text-orange-600'
|
||||
}`}
|
||||
>
|
||||
{topic.isSignedIn ? '已签到' : '未签到'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
export default typeof definePageConfig === 'function'
|
||||
? definePageConfig({
|
||||
navigationBarTitleText: '我的',
|
||||
navigationBarBackgroundColor: '#111827',
|
||||
navigationBarTextStyle: 'white'
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
: {
|
||||
navigationBarTitleText: '我的',
|
||||
navigationBarBackgroundColor: '#111827',
|
||||
navigationBarTextStyle: 'white'
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black'
|
||||
}
|
||||
|
||||
@@ -4,98 +4,125 @@ import { FC, useState } from 'react'
|
||||
import { Network } from '@/network'
|
||||
import './index.css'
|
||||
|
||||
interface SignInStatus {
|
||||
todaySignedIn: boolean
|
||||
continuousDays: number
|
||||
interface UserInfo {
|
||||
nickname: string
|
||||
avatar: string
|
||||
userId: string
|
||||
}
|
||||
|
||||
interface UserStats {
|
||||
totalTopics: number
|
||||
totalDays: number
|
||||
maxContinuousDays: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 个人中心页面
|
||||
*/
|
||||
const ProfilePage: FC = () => {
|
||||
const [signInStatus, setSignInStatus] = useState<SignInStatus>({
|
||||
todaySignedIn: false,
|
||||
continuousDays: 0,
|
||||
totalDays: 0
|
||||
const [userInfo, setUserInfo] = useState<UserInfo>({
|
||||
nickname: '未登录',
|
||||
avatar: '',
|
||||
userId: ''
|
||||
})
|
||||
const [stats, setStats] = useState<UserStats>({
|
||||
totalTopics: 0,
|
||||
totalDays: 0,
|
||||
maxContinuousDays: 0
|
||||
})
|
||||
|
||||
// 页面显示时获取签到统计
|
||||
// 页面显示时获取用户信息
|
||||
useDidShow(async () => {
|
||||
await fetchSignInStatus()
|
||||
await fetchUserInfo()
|
||||
})
|
||||
|
||||
// 获取签到状态
|
||||
const fetchSignInStatus = async () => {
|
||||
// 获取用户信息
|
||||
const fetchUserInfo = async () => {
|
||||
try {
|
||||
const res = await Network.request({
|
||||
url: '/api/signin/status',
|
||||
url: '/api/user/info',
|
||||
method: 'GET'
|
||||
})
|
||||
console.log('签到状态:', res.data)
|
||||
console.log('用户信息:', res.data)
|
||||
if (res.data?.code === 200 && res.data?.data) {
|
||||
setSignInStatus(res.data.data)
|
||||
setUserInfo({
|
||||
nickname: res.data.data.nickname || '未登录',
|
||||
avatar: res.data.data.avatar || '',
|
||||
userId: res.data.data.userId || ''
|
||||
})
|
||||
setStats({
|
||||
totalTopics: res.data.data.totalTopics || 0,
|
||||
totalDays: res.data.data.totalDays || 0,
|
||||
maxContinuousDays: res.data.data.maxContinuousDays || 0
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取签到状态失败:', error)
|
||||
console.error('获取用户信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="min-h-screen bg-gray-900 px-4 py-6">
|
||||
<View className="min-h-screen bg-gray-50 px-4 py-4">
|
||||
{/* 用户信息卡片 */}
|
||||
<View className="bg-gradient-to-br from-blue-600 to-purple-600 rounded-2xl p-6 mb-6">
|
||||
<View className="flex flex-row items-center gap-4 mb-4">
|
||||
<View className="w-16 h-16 rounded-full bg-white bg-opacity-20 flex items-center justify-center">
|
||||
<Text className="text-white text-2xl">👤</Text>
|
||||
<View className="bg-gradient-to-r from-orange-500 to-orange-400 rounded-lg p-4 mb-4">
|
||||
<View className="flex flex-row items-center gap-3">
|
||||
<View className="w-14 h-14 rounded-full bg-white bg-opacity-20 flex items-center justify-center overflow-hidden">
|
||||
{userInfo.avatar ? (
|
||||
<View className="w-full h-full bg-gray-300" />
|
||||
) : (
|
||||
<Text className="text-white text-2xl">👤</Text>
|
||||
)}
|
||||
</View>
|
||||
<View className="flex-1">
|
||||
<Text className="block text-white text-xl font-bold">微博用户</Text>
|
||||
<Text className="block text-blue-200 text-sm mt-1">ID: default_user</Text>
|
||||
<Text className="block text-white text-lg font-semibold">{userInfo.nickname}</Text>
|
||||
{userInfo.userId && (
|
||||
<Text className="block text-orange-100 text-xs mt-1">ID: {userInfo.userId}</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 签到统计 */}
|
||||
<View className="mb-6">
|
||||
<Text className="block text-white text-lg font-bold mb-4">签到统计</Text>
|
||||
{/* 统计信息 */}
|
||||
<View className="bg-white rounded-lg p-4 mb-4 border border-gray-100">
|
||||
<Text className="block text-gray-900 font-semibold mb-3">我的统计</Text>
|
||||
<View className="flex flex-row gap-3">
|
||||
<View className="flex-1 bg-gray-800 rounded-2xl p-4">
|
||||
<Text className="block text-gray-400 text-xs">连续签到</Text>
|
||||
<Text className="block text-white text-3xl font-bold mt-1">{signInStatus.continuousDays}</Text>
|
||||
<Text className="block text-gray-400 text-xs mt-1">天</Text>
|
||||
<View className="flex-1 text-center">
|
||||
<Text className="block text-gray-900 text-2xl font-bold">{stats.totalTopics}</Text>
|
||||
<Text className="block text-gray-400 text-xs mt-1">关注超话</Text>
|
||||
</View>
|
||||
<View className="flex-1 bg-gray-800 rounded-2xl p-4">
|
||||
<Text className="block text-gray-400 text-xs">累计签到</Text>
|
||||
<Text className="block text-white text-3xl font-bold mt-1">{signInStatus.totalDays}</Text>
|
||||
<Text className="block text-gray-400 text-xs mt-1">天</Text>
|
||||
<View className="w-px bg-gray-100" />
|
||||
<View className="flex-1 text-center">
|
||||
<Text className="block text-gray-900 text-2xl font-bold">{stats.totalDays}</Text>
|
||||
<Text className="block text-gray-400 text-xs mt-1">累计签到</Text>
|
||||
</View>
|
||||
<View className="w-px bg-gray-100" />
|
||||
<View className="flex-1 text-center">
|
||||
<Text className="block text-gray-900 text-2xl font-bold">{stats.maxContinuousDays}</Text>
|
||||
<Text className="block text-gray-400 text-xs mt-1">最长连续</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 今日状态 */}
|
||||
<View className="bg-gray-800 rounded-2xl p-4 mb-6">
|
||||
<View className="flex flex-row items-center justify-between">
|
||||
<View>
|
||||
<Text className="block text-white text-base font-semibold">今日签到</Text>
|
||||
<Text className="block text-gray-400 text-sm mt-1">
|
||||
{signInStatus.todaySignedIn ? '已完成' : '未完成'}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`w-12 h-12 rounded-full flex items-center justify-center ${
|
||||
signInStatus.todaySignedIn ? 'bg-green-500' : 'bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
<Text className="text-white text-xl">{signInStatus.todaySignedIn ? '✓' : '○'}</Text>
|
||||
</View>
|
||||
{/* 功能菜单 */}
|
||||
<View className="bg-white rounded-lg border border-gray-100 mb-4">
|
||||
<View className="flex flex-row items-center justify-between p-4 border-b border-gray-50">
|
||||
<Text className="text-gray-700 text-sm">使用说明</Text>
|
||||
<Text className="text-gray-400 text-xs">→</Text>
|
||||
</View>
|
||||
<View className="flex flex-row items-center justify-between p-4 border-b border-gray-50">
|
||||
<Text className="text-gray-700 text-sm">关于我们</Text>
|
||||
<Text className="text-gray-400 text-xs">→</Text>
|
||||
</View>
|
||||
<View className="flex flex-row items-center justify-between p-4">
|
||||
<Text className="text-gray-700 text-sm">版本信息</Text>
|
||||
<Text className="text-gray-400 text-xs">v1.0.0</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 提示信息 */}
|
||||
<View className="bg-gray-800 rounded-2xl p-4">
|
||||
<Text className="block text-gray-400 text-sm leading-relaxed">
|
||||
温馨提示:每天签到可以点亮一颗星星,连续签到让星空更加璀璨。坚持签到,养成好习惯!
|
||||
<View className="bg-orange-50 rounded-lg p-4">
|
||||
<Text className="block text-orange-600 text-xs leading-relaxed">
|
||||
温馨提示:本工具需要登录微博账号才能使用。签到功能依赖于微博官方接口,请合理使用。
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
export default typeof definePageConfig === 'function'
|
||||
? definePageConfig({
|
||||
navigationBarTitleText: '签到记录',
|
||||
navigationBarBackgroundColor: '#111827',
|
||||
navigationBarTextStyle: 'white'
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
: {
|
||||
navigationBarTitleText: '签到记录',
|
||||
navigationBarBackgroundColor: '#111827',
|
||||
navigationBarTextStyle: 'white'
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black'
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import './index.css'
|
||||
|
||||
interface SignInRecord {
|
||||
id: number
|
||||
user_id: string
|
||||
sign_date: string
|
||||
created_at: string
|
||||
date: string
|
||||
count: number
|
||||
details: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -16,9 +16,13 @@ interface SignInRecord {
|
||||
*/
|
||||
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()
|
||||
})
|
||||
@@ -28,12 +32,16 @@ const RecordPage: FC = () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const res = await Network.request({
|
||||
url: '/api/signin/history',
|
||||
url: '/api/super-topics/records',
|
||||
method: 'GET'
|
||||
})
|
||||
console.log('签到记录:', res.data)
|
||||
if (res.data?.code === 200 && res.data?.data) {
|
||||
setRecords(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)
|
||||
@@ -42,15 +50,14 @@ const RecordPage: FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日期显示
|
||||
// 格式化日期
|
||||
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}`
|
||||
return `${month}月${day}日 ${weekDay}`
|
||||
}
|
||||
|
||||
// 获取当前月份的天数
|
||||
@@ -60,19 +67,22 @@ const RecordPage: FC = () => {
|
||||
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 }[] = []
|
||||
const days: { date: Date | null; hasRecord: boolean }[] = []
|
||||
|
||||
// 填充前面的空白
|
||||
for (let i = 0; i < firstDay.getDay(); i++) {
|
||||
days.push({ date: null as any, isSigned: false })
|
||||
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 isSigned = records.some(r => r.sign_date === dateStr)
|
||||
days.push({ date, isSigned })
|
||||
const hasRecord = recordDates.includes(dateStr)
|
||||
days.push({ date, hasRecord })
|
||||
}
|
||||
|
||||
return days
|
||||
@@ -83,19 +93,32 @@ const RecordPage: FC = () => {
|
||||
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 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-gray-800 rounded-2xl p-4 mb-6">
|
||||
<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-3">
|
||||
<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-sm">{day}</Text>
|
||||
<Text className="text-gray-400 text-xs">{day}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
@@ -106,20 +129,20 @@ const RecordPage: FC = () => {
|
||||
<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'
|
||||
className={`w-6 h-6 rounded-full flex items-center justify-center ${
|
||||
item.hasRecord
|
||||
? 'bg-orange-500'
|
||||
: item.date.toDateString() === now.toDateString()
|
||||
? 'bg-gray-700 border border-blue-400'
|
||||
? 'bg-orange-100'
|
||||
: 'bg-transparent'
|
||||
}`}
|
||||
>
|
||||
<Text
|
||||
className={`text-sm ${
|
||||
item.isSigned
|
||||
className={`text-xs ${
|
||||
item.hasRecord
|
||||
? 'text-white'
|
||||
: item.date.toDateString() === now.toDateString()
|
||||
? 'text-blue-400'
|
||||
? 'text-orange-600'
|
||||
: 'text-gray-400'
|
||||
}`}
|
||||
>
|
||||
@@ -135,28 +158,27 @@ const RecordPage: FC = () => {
|
||||
</View>
|
||||
|
||||
{/* 签到记录列表 */}
|
||||
<View className="bg-gray-800 rounded-2xl p-4">
|
||||
<Text className="block text-white text-lg font-bold mb-4">签到记录</Text>
|
||||
<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-500">加载中...</Text>
|
||||
<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-500 text-base">暂无签到记录</Text>
|
||||
<Text className="text-gray-600 text-sm mt-2">开始你的第一次签到吧</Text>
|
||||
<Text className="text-gray-400 text-sm">暂无签到记录</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 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-white text-sm">{formatDate(record.sign_date)}</Text>
|
||||
<Text className="block text-gray-700 text-sm">{formatDate(record.date)}</Text>
|
||||
</View>
|
||||
<View className="bg-blue-500 rounded-full px-3 py-1">
|
||||
<Text className="text-white text-xs">已签到</Text>
|
||||
<View className="bg-orange-50 rounded-full px-2 py-0.5">
|
||||
<Text className="text-orange-600 text-xs">{record.count} 个超话</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user