Files
uniapp-im-shop/pages/discover/punch.vue

369 lines
9.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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)
}
/**
* 生成一个 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)
})
}
// 填充下个月的前几天,使总数达到 355*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>
<!-- 签到 -->
<view
v-for="(item, index) in dateList"
:key="index"
:class="{ active: indexData.includes(item.signDate) }"
class="item"
@click="onItem(item)"
>
<view class="bg-box">
<image
src="/static/images/discover/bean.png"
mode="heightFix"
class="icon"
></image>
<!-- <text>+10</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>