@@ -0,0 +1,417 @@
< template >
< view class = "" >
< uni-popup ref = "popup" >
< view class = "popup-content" @touchmove.stop >
< view class = "fl1" > < / view >
< view class = "max-div" >
< view class = "popup_back_div" @click ="close" >
< image :src = "optionErrorIcon('#C8BBB7')" class = "img" > < / image >
< / view >
< view class = "title_div" >
< image class = "img" src = "@/static/images/pointsAndRank/badge-wheat-left.png" > < / image >
< view class = "title" > 每日签到 < / view >
< image class = "img" src = "@/static/images/pointsAndRank/badge-wheat-right.png" > < / image >
< / view >
< view class = "continuous-check-in-div" >
< image class = "img" src = "@/static/images/pointsAndRank/badge-icon.png" > < / image >
< text class = "text" >
您已连续签到
< text class = "number" > 27 < / text >
天
< / text >
< / view >
< scroll-view scroll -y = " true " class = "scroll-Y" :show-scrollbar = "false" >
< view class = "calendar-div" >
< view class = "calendar-line" v-for = "(line, lineIndex) in dateGroups" >
< view
class = "calendar-day"
v-for = "(day, dayIndex) in line.days"
: class = "{ hide: day.index > 30, expand: expand }"
>
< view class = "calendar-day-bg" : class = "[day.type, isSigned ? 'signed' : 'no-signed']" >
< image
v-if = "day.type === 'past' || (day.type === 'today' && isSigned)"
:src = "optionSuccessIcon('#FFFFFF')"
class = "img"
> < / image >
< text v-else > + {{ day.text }} < / text >
< view class = "check-in-tip" > 签 < / view >
< / view >
< view class = "calendar-day-title" >
{ { day . date } }
< / view >
< / view >
< / view >
< / view >
< / scroll-view >
< view class = "switch-div" @click ="switchStatus()" >
< view class = "back_div" >
< view class = "back-icon" > < / view >
< / view >
< / view >
< view class = "button-div" >
< uv-button
class = "button"
text = "签到"
:custom-style = "customStyle"
:color = "buttonColor"
@click ="expand = !expand"
> < / uv-button >
< / view >
< / view >
< view class = "fl1" > < / view >
< / view >
< / uni-popup >
< / view >
< / template >
< script setup >
import { ref } from 'vue' ;
import { optionErrorIcon , optionSuccessIcon } from '@/common/imgSvg' ;
const popup = ref ( null ) ;
const buttonColor = 'linear-gradient( 270deg, #E0914A 0%, #EDBE7F 100%)' ;
const customStyle = {
borderRadius : '40rpx' //圆角
} ;
const dateGroups = ref ( [ ] ) ;
const expand = ref ( true ) ; // 展开状态
const isSigned = ref ( false ) ; // 今日签到状态
// 切换显示状态
const switchStatus = ( ) => {
}
// 退出签到
const close = ( ) => {
popup . value . close ( ) ;
}
/**
* 生成35天日期列表(分5组,每组7天),包含类型标记和text字段
* 以"前X天"为第一天,第1天显示1,每7天显示5,第30天显示30
* @param {number} X - 前X天的天数(0-35之间的整数,总天数=35)
* @returns {Array<{
* groupIndex: number, // 组索引(0-4)
* days: Array<{
* date: string, // mm-dd格式日期
* type: 'past' | 'today' | 'future', // 日期类型
* index: number, // 相对今天的索引(负数为过去,正数为未来)
* text: string // 显示文本(1/5/30)
* }>
* }>} 分组后的日期列表
*/
function generateGroupedDateList ( X ) {
if ( X < 0 || X > 35 || ! Number . isInteger ( X ) ) {
throw new Error ( 'X必须是0到35之间的整数' ) ;
}
const allDays = [ ] ;
const today = new Date ( ) ;
const todayDate = new Date ( today . getFullYear ( ) , today . getMonth ( ) , today . getDate ( ) ) ;
// 生成前X天到后(35-X)天的所有日期(共35天)
for ( let i = - X ; i <= 35 - X - 1 ; i ++ ) {
const currentDate = new Date ( today ) ;
currentDate . setDate ( today . getDate ( ) + i ) ;
const pureDate = new Date ( currentDate . getFullYear ( ) , currentDate . getMonth ( ) , currentDate . getDate ( ) ) ;
// 确定日期类型
let type ;
if ( pureDate < todayDate ) type = 'past' ;
else if ( pureDate > todayDate ) type = 'future' ;
else type = 'today' ;
// 格式化日期
const month = String ( currentDate . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
const day = String ( currentDate . getDate ( ) ) . padStart ( 2 , '0' ) ;
// 计算当前是第几天(以"前X天"为第1天)
const dayNumber = i - - X + 1 ; // 核心计算:转换为从1开始的序列
// 计算text字段
let text = '1' ; // 默认显示1
if ( dayNumber === 30 ) {
text = '30' ; // 第30天显示30
} else if ( ( dayNumber % 30 ) % 7 === 0 ) {
text = '5' ; // 每7天(第7、14、21、28、35天)显示5
}
allDays . push ( {
date : ` ${ month } . ${ day } ` ,
type ,
index : i - - X + 1 ,
text ,
i
} ) ;
}
// 每7天分为一组,共5组
const groupedList = [ ] ;
for ( let i = 0 ; i < 5 ; i ++ ) {
const start = i * 7 ;
const end = start + 7 ;
groupedList . push ( {
groupIndex : i ,
days : allDays . slice ( start , end )
} ) ;
}
return groupedList ;
}
// 示例:X=10时,以"10天前"为第1天,生成35天列表
try {
const X = 10 ;
dateGroups . value = generateGroupedDateList ( X ) ;
console . log ( ` 以 ${ X } 天前为第1天的35天分组日期: ` ) ;
dateGroups . value . forEach ( ( group ) => {
console . log ( ` \ n第 ${ group . groupIndex + 1 } 组: ` ) ;
console . log (
group . days . map ( ( day ) => ( {
日期 : day . date ,
天数序号 : day . index , // 显示当前是第几天
text : day . text ,
类型 : day . type
} ) )
) ;
} ) ;
} catch ( err ) {
console . error ( err . message ) ;
}
const open = ( ) => {
console . log ( '111111111' ) ;
popup . value . open ( ) ;
} ;
defineExpose ( { open , close } ) ;
< / script >
< style scoped lang = "scss" >
// 日期
. calendar - day {
text - align : center ;
// 隐藏展开时超过30天的样式
& . hide . expand {
. calendar - day - title {
width : 70 rpx ;
color : # fff ;
}
. calendar - day - bg {
display : none ;
}
}
. calendar - day - bg {
width : 70 rpx ;
height : 84 rpx ;
border - radius : 8 rpx ;
// 前面日期,代表已签到
display : flex ;
align - items : center ;
justify - content : center ;
position : relative ;
font - family : Arial , Arial ;
font - size : 32 rpx ;
. img {
width : 32 rpx ;
margin - bottom : 6 rpx ;
}
& . past {
background - color : # ffd07c ;
}
// 未来日期,代表未签到
& . future {
color : # d88f50 ;
font - weight : bold ;
background : # ffe6b8 ;
border : 4 rpx solid # ffd58a ;
}
// 当前已签到和未签到
& . signed . today {
background - color : # ffd07c ;
}
& . no - signed . today {
color : # d88f50 ;
font - weight : bold ;
background : # ffe6b8 ;
border : 4 rpx solid # ffd58a ;
}
// 当天的签到小图标
. check - in - tip {
display : none ;
}
& . today > . check - in - tip {
display : block ;
position : absolute ;
top : 0 ;
right : 0 ;
width : 30 rpx ;
height : 30 rpx ;
font - size : 20 rpx ;
border - radius : 50 % ;
border : 2 rpx solid # fff ;
box - shadow : 0 0 4 rpx # fef5e6 ;
color : # fff ;
font - weight : 400 ;
background - color : # f8831c ;
display : flex ;
justify - content : center ; /* 水平居中 */
align - items : center ; /* 垂直居中 */
/* 2. 解决文字被边框挤压:清除默认内边距,重置行高 */
padding : 0 ; /* 清除默认内边距(部分元素默认有padding) */
line - height : 1 ; /* 行高与字体大小一致,避免垂直方向多余空间 */
/* 3. 旋转30度(transform 旋转属性) */
transform : translate ( 30 % , - 40 % ) rotate ( - 30 deg ) ;
}
}
. calendar - day - title {
margin : 8 rpx 0 ;
font - family : ArialMT ;
font - size : 22 rpx ;
color : # 999999 ;
line - height : 28 rpx ;
text - align : center ;
font - style : normal ;
}
}
. scroll - Y {
/**
* 26rpx 主盒子内间距
* 68rpx 标题高度
* 128rpx 日历高度
* 16rpx 日历下外间距
* 92rpx 下方按钮盒子高度
* 80rpx 切换盒子高度
*/
height : calc ( 100 % - 26 rpx - 68 rpx - 128 rpx - 16 rpx - 92 rpx - 80 rpx - 8 rpx ) ;
}
. switch - div {
display : flex ;
justify - content : center ;
align - items : center ;
height : 60 rpx ;
margin : 10 rpx 30 % ;
. back _div {
. back - icon {
position : relative ;
width : 40 rpx ;
height : 40 rpx ;
cursor : pointer ;
}
. back - icon : : before ,
. back - icon : : after {
content : '' ;
position : absolute ;
transition : all 0.3 s ease ;
}
. back - icon : : after {
top : 25 % ;
left : 25 % ;
width : 40 % ;
height : 40 % ;
border - top : 2 rpx solid # bfbfbf ;
border - left : 2 rpx solid # bfbfbf ;
transform : rotate ( 45 deg ) ;
}
}
}
. calendar - div {
display : flex ;
flex - direction : column ;
. calendar - line {
display : flex ;
justify - content : space - between ;
}
}
. button - div {
height : 92 rpx ;
width : 442 rpx ;
margin : 0 auto ;
. button {
width : 100 % ;
height : 100 % ;
}
}
. continuous - check - in - div {
display : flex ;
border - bottom : 2 rpx solid # efefef ;
margin - bottom : 16 rpx ;
padding - top : 8 rpx ;
height : 128 rpx ;
. img {
width : 112 rpx ;
height : 102 rpx ;
}
. text {
margin - left : 16 rpx ;
font - weight : 400 ;
font - size : 30 rpx ;
height : 82 rpx ;
color : # 333333 ;
display : flex ;
align - items : flex - end ;
}
. number {
font - weight : 600 ;
color : # 0066 ff ;
font - family : Arial Black ;
}
}
. title _div {
display : flex ;
align - items : center ;
justify - content : center ;
margin - top : 34 rpx ;
height : 68 rpx ;
. img {
width : 26 rpx ;
height : 44 rpx ;
}
. title {
font - family : AlimamaShuHeiTi , AlimamaShuHeiTi ;
font - weight : bold ;
font - size : 46 rpx ;
color : # d88f50 ;
margin : 0 rpx 30 rpx ;
text - align : center ;
}
}
. popup _back _div {
width : 60 rpx ;
height : 60 rpx ;
position : absolute ;
top : 20 rpx ;
right : 20 rpx ;
. img {
margin : 10 rpx ;
width : 40 rpx ;
height : 40 rpx ;
}
}
. popup - content {
width : 100 % ;
height : 100 vh ;
display : flex ;
flex - direction : column ;
align - items : center ;
padding : 40 rpx 0 ;
}
. max - div {
width : 80 vw ;
background - color : # fefbfa ;
border - radius : 38 rpx ;
position : relative ;
padding : 26 rpx ;
overflow : hidden ;
margin - top : var ( -- status - bar - height ) ;
margin - bottom : 120 rpx ;
}
< / style >