383 lines
9.9 KiB
Vue
383 lines
9.9 KiB
Vue
<script setup>
|
||
import { ref } from 'vue'
|
||
import { formatMonthDay, formatDate } from '@/utils/dateUtils'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import { getSignList, signIn } from '@/api'
|
||
import { useUI } from '@/utils/use-ui'
|
||
import { useAuthUser } from '@/composables/useAuthUser'
|
||
import { useUserStore } from '@/stores/user'
|
||
import { formatNumberWithWan } from '../../utils'
|
||
|
||
const { showToast } = useUI()
|
||
const { integralData } = useAuthUser()
|
||
const { getIntegral } = useUserStore()
|
||
|
||
const weekList = [
|
||
{ name: '一', value: 1 },
|
||
{ name: '二', value: 2 },
|
||
{ name: '三', value: 3 },
|
||
{ name: '四', value: 4 },
|
||
{ name: '五', value: 5 },
|
||
{ name: '六', value: 6 },
|
||
{ name: '日', value: 7 }
|
||
]
|
||
const indexData = ref([])
|
||
|
||
const currentYear = ref(2025)
|
||
const currentMonth = ref(12)
|
||
/** 日期列表 */
|
||
const dateList = ref([])
|
||
/** 连续天数 */
|
||
const continuousDays = ref(0)
|
||
|
||
/** 给数子补零 */
|
||
const padZero = num => {
|
||
return num < 10 ? `0${num}` : num
|
||
}
|
||
|
||
const getData = async msg => {
|
||
const res = await getSignList({
|
||
signMonth: `${currentYear.value}-${padZero(currentMonth.value)}`
|
||
})
|
||
|
||
dateList.value = generateCalendar(
|
||
currentYear.value,
|
||
currentMonth.value
|
||
)
|
||
|
||
if (msg) {
|
||
await getIntegral()
|
||
showToast(msg)
|
||
}
|
||
|
||
continuousDays.value = res.data.continuousDays
|
||
// indexData.value = res.data.list.map(v => v.signDate)
|
||
indexData.value = res.data.list
|
||
|
||
console.log(indexData.value, '===')
|
||
}
|
||
|
||
/** 判断那一天签到了 */
|
||
const isSign = item => {
|
||
return indexData.value.some(v => v.signDate === item.signDate)
|
||
}
|
||
|
||
/** 判断签到那一天加多少积分 */
|
||
const isAddIntegral = item => {
|
||
return indexData.value.find(v => v.signDate === item.signDate)
|
||
.pointsAwarded
|
||
}
|
||
|
||
/**
|
||
* 生成一个 5x7 的日历数组(从周一到周日)
|
||
* @param {number} year - 当前年份
|
||
* @param {number} month - 当前月份(1-12)
|
||
* @returns {Array<{ date: Date, type: 'prev' | 'current' | 'next' }>}
|
||
*/
|
||
const generateCalendar = (year, month) => {
|
||
const today = new Date()
|
||
const todayYear = today.getFullYear()
|
||
const todayMonth = today.getMonth()
|
||
const todayDate = today.getDate()
|
||
// 辅助函数:判断两个 Date 是否是同一天(忽略时分秒)
|
||
const isSameDay = (d1, d2) => {
|
||
return (
|
||
d1.getFullYear() === d2.getFullYear() &&
|
||
d1.getMonth() === d2.getMonth() &&
|
||
d1.getDate() === d2.getDate()
|
||
)
|
||
}
|
||
|
||
// 创建当前月的第一天
|
||
const firstDay = new Date(year, month - 1, 1)
|
||
// 创建当前月的最后一天
|
||
const lastDay = new Date(year, month, 0)
|
||
|
||
// 获取当前月第一天是星期几
|
||
let startDayOfWeek = firstDay.getDay()
|
||
// 转换为以周一为 0:如果 getDay() 是 0(周日),则视为 6(即一周最后一天)
|
||
if (startDayOfWeek === 0) startDayOfWeek = 7
|
||
const offsetFromMonday = startDayOfWeek - 1 // 周一为0
|
||
|
||
// 上个月的最后一天
|
||
const prevMonthLastDay = new Date(year, month - 1, 0).getDate()
|
||
|
||
// 当前月总天数
|
||
const currentMonthDays = lastDay.getDate()
|
||
|
||
const calendar = []
|
||
// 仅用于比较
|
||
const todayRef = new Date(todayYear, todayMonth, todayDate)
|
||
|
||
// 填充上个月的尾几天
|
||
for (let i = 0; i < offsetFromMonday; i++) {
|
||
const day = prevMonthLastDay - offsetFromMonday + 1 + i
|
||
|
||
const date = new Date(year, month - 2, day) // month - 2 表示上个月
|
||
calendar.push({
|
||
date: formatMonthDay(date),
|
||
signDate: formatDate(date),
|
||
type: 'prev',
|
||
isToday: isSameDay(date, todayRef)
|
||
})
|
||
}
|
||
|
||
// 填充当前月
|
||
for (let i = 0; i < currentMonthDays; i++) {
|
||
const date = new Date(year, month - 1, i + 1)
|
||
|
||
calendar.push({
|
||
date: formatMonthDay(date),
|
||
signDate: formatDate(date),
|
||
type: 'current',
|
||
isToday: isSameDay(date, todayRef)
|
||
})
|
||
}
|
||
|
||
// 填充下个月的前几天,使总数达到 35(5*7)
|
||
const remaining = 35 - calendar.length
|
||
for (let i = 1; i <= remaining; i++) {
|
||
const date = new Date(year, month, i) // month 是下个月(因为 JS 月份从 0 开始)
|
||
calendar.push({
|
||
date: formatMonthDay(date),
|
||
signDate: formatDate(date),
|
||
type: 'next',
|
||
isToday: isSameDay(date, todayRef)
|
||
})
|
||
}
|
||
|
||
return calendar
|
||
}
|
||
|
||
/** 点击签到 */
|
||
const onItem = async item => {
|
||
if (!item.isToday) return
|
||
const res = await signIn()
|
||
await getData(res.data.message)
|
||
}
|
||
|
||
/** 日期切换 */
|
||
const dateChange = e => {
|
||
if (e === 0) {
|
||
if (currentMonth.value === 1) {
|
||
currentYear.value--
|
||
currentMonth.value = 12
|
||
} else {
|
||
currentMonth.value--
|
||
}
|
||
} else {
|
||
if (currentMonth.value === 12) {
|
||
currentYear.value++
|
||
currentMonth.value = 1
|
||
} else {
|
||
currentMonth.value++
|
||
}
|
||
}
|
||
getData()
|
||
}
|
||
|
||
onLoad(() => {
|
||
const now = new Date()
|
||
currentYear.value = now.getFullYear()
|
||
currentMonth.value = now.getMonth() + 1
|
||
|
||
getData()
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<view class="punch">
|
||
<nav-bar>
|
||
<template #back>
|
||
<image
|
||
src="/static/images/public/return-icon.png"
|
||
mode="heightFix"
|
||
class="top-left-icon"
|
||
></image>
|
||
</template>
|
||
</nav-bar>
|
||
<view class="public-header—box">
|
||
<view class="integral-box">
|
||
<text>我的积分</text>
|
||
<text style="font-size: 80rpx; margin-top: 30rpx">
|
||
{{ formatNumberWithWan(integralData) }}
|
||
</text>
|
||
</view>
|
||
<image
|
||
src="/static/images/discover/calendar.png"
|
||
mode="aspectFit"
|
||
class="right-icon"
|
||
></image>
|
||
</view>
|
||
|
||
<view class="punch-box">
|
||
<view class="top-title">
|
||
<text class="title">每日签到领积分</text>
|
||
<view class="right-box">
|
||
<text>已连续签到</text>
|
||
<text>{{ continuousDays }}</text>
|
||
<text>天</text>
|
||
</view>
|
||
</view>
|
||
<!-- 切换 -->
|
||
<view class="switch-box">
|
||
<text class="date">
|
||
{{ currentYear }}年{{ padZero(currentMonth) }}月
|
||
</text>
|
||
<view class="btn">
|
||
<uni-icons
|
||
type="left"
|
||
size="22"
|
||
color="#666666"
|
||
@click="dateChange(0)"
|
||
></uni-icons>
|
||
<uni-icons
|
||
type="right"
|
||
size="22"
|
||
color="#666666"
|
||
@click="dateChange(1)"
|
||
></uni-icons>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 签到列表 -->
|
||
<view class="list-box">
|
||
<!-- 星期列表 -->
|
||
<view v-for="(item, index) in weekList" :key="index" class="item">
|
||
<text class="bottom-name">{{ item.name }}</text>
|
||
</view>
|
||
<!-- 签到 :class="{ active: indexData.includes(item.signDate) }" -->
|
||
<view
|
||
v-for="(item, index) in dateList"
|
||
:key="index"
|
||
:class="{ active: isSign(item) }"
|
||
class="item"
|
||
@click="onItem(item)"
|
||
>
|
||
<view class="bg-box">
|
||
<image
|
||
src="/static/images/discover/bean.png"
|
||
mode="heightFix"
|
||
class="icon"
|
||
></image>
|
||
<text v-if="isSign(item)">+{{ isAddIntegral(item) }}</text>
|
||
</view>
|
||
<text
|
||
:style="{ color: item.isToday ? '#00d993' : '#999999' }"
|
||
class="bottom-name"
|
||
>
|
||
{{ item.isToday ? '今日' : item.date }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
@import './styles/index.scss';
|
||
.right-icon {
|
||
right: -86rpx !important;
|
||
}
|
||
.punch-box {
|
||
padding: 0 24rpx;
|
||
.top-title {
|
||
font-family: PingFang SC, PingFang SC;
|
||
font-style: normal;
|
||
text-transform: none;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
.title {
|
||
font-weight: bold;
|
||
font-size: 32rpx;
|
||
color: #333333;
|
||
}
|
||
.right-box {
|
||
margin-left: 32rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
text {
|
||
font-weight: 500;
|
||
font-size: 28rpx;
|
||
color: #666666;
|
||
&:nth-child(2) {
|
||
margin: 0 4rpx;
|
||
color: #00d993;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.switch-box {
|
||
margin: 50rpx 0 40rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
.date {
|
||
font-family: PingFang SC, PingFang SC;
|
||
font-weight: 500;
|
||
font-size: 32rpx;
|
||
color: #666666;
|
||
font-style: normal;
|
||
text-transform: none;
|
||
}
|
||
.btn {
|
||
width: 180rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
}
|
||
|
||
.list-box {
|
||
margin-top: 38rpx;
|
||
font-family: PingFang SC, PingFang SC;
|
||
font-style: normal;
|
||
text-transform: none;
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
grid-gap: 22rpx;
|
||
.item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
.bg-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
width: 80rpx;
|
||
height: 100rpx;
|
||
background: #f4f4f4;
|
||
border-radius: 8rpx;
|
||
.icon {
|
||
height: 48rpx;
|
||
}
|
||
text {
|
||
font-weight: bold;
|
||
font-size: 24rpx;
|
||
color: #999999;
|
||
}
|
||
}
|
||
.bottom-name {
|
||
margin-top: 8rpx;
|
||
font-size: 24rpx;
|
||
color: #999999;
|
||
}
|
||
}
|
||
.active {
|
||
.bg-box {
|
||
background: linear-gradient(0deg, #00d993 0%, #00d9c5 100%);
|
||
text {
|
||
color: #ffffff;
|
||
}
|
||
}
|
||
.bottom-name {
|
||
color: #00d993;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|