持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情
“金秋十月,我要连续30天更文,做劳模,拿手机摄影神器!点击查看活动详情 “即可成功参与
最近的需求是实现类似钉钉打卡的功能,今天实现页面的构建,后续提供交互逻辑,
日历组件
html结构:
<template>
<div>
<div class="top-title">
<div><span @click="lastMonth" class="link">⋘</span></div>
<div><span>{{year}}年{{month}}月</span></div>
<div><span @click="nextMonth" class="link">⋙</span></div>
</div>
<div class="containerBox">
<div v-for="(item,index) in weeks" :key="index">{{ item }}</div>
</div>
<div class="containerBox" style="padding: 1vh 1vh 3vh 1vh;">
<div v-for="(item,index) in data" :key="index">
<!-- 当前日期 -->
<div v-if="compareToNow(item) === 0" style="color: #fff" class="nowDay">{{ item.date }}</div>
<!-- 当前以后的日期 -->
<div v-if="compareToNow(item) === 1">{{ item.date }}
</div>
<!-- 当前以前的日期 -->
<div v-if="compareToNow(item) === -1" class="otherDay">
<div>{{ item.date }}</div>
<!-- <div class="date-desc">补卡</div> -->
</div>
</div>
</div>
</div>
</template>
js核心代码:
import { ref } from 'vue';
//打卡日历
const now = new Date()
const weeks = ["日", "一", "二", "三", "四", "五", "六"]
const year: any = ref('')
const month: any = ref('')
const date: any = ref('')
const firstDay: any = ref('')
const data: any = ref([])
const getNow = () => {
year.value = now.getFullYear()
month.value = now.getMonth() + 1;
date.value = now.getDate();
now.setDate(1);
firstDay.value = now.getDay();
initData();
}
const getMonthDay = (month: any) => {
if ([1, 3, 5, 7, 8, 10, 12].includes(month)) {
return 31
} else if ([4, 6, 9, 11].includes(month)) {
return 30
} else if (month === 2) {
// 判断当年是否为闰年
if (
(year.value % 4 === 0 && year.value % 100 !== 0) ||
year.value % 400 === 0
) {
return 29
} else {
return 28
}
}
}
const initData = () => {
data.value = []
let days: any = getMonthDay(month.value);
for (let i = 0; i < firstDay.value; i++) {
data.value.push({
year: "",
month: "",
date: "",
});
}
for (let i = 0; i < days; i++) {
data.value.push(
{
year: year.value,
month: month.value,
date: i + 1,
}
);
}
}
const lastMonth = () => {
now.setMonth(now.getMonth() - 1);
getNow();
}
const nextMonth = () => {
now.setMonth(now.getMonth() + 1);
getNow();
}
const compareToNow = (item: any) => {
// console.log(item, 'item')
if (item.year && item.month && item.date) {
let date1 = new Date();
date1.setFullYear(item.year)
date1.setMonth(item.month - 1)
date1.setDate(item.date)
date1.setHours(0)
date1.setMinutes(0)
date1.setSeconds(0)
let now = new Date();
now.setHours(0)
now.setMinutes(0)
now.setSeconds(0)
if (date1.getTime() > now.getTime()) {
return 1
} else if (date1.getTime() === now.getTime()) {
return 0
} else if (date1.getTime() < now.getTime()) {
return -1
}
}
}
getNow()
scc样式:
//日历
.top-title {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 50px;
grid-gap: 1rem;
background-color: #FFFFFF;
// border-bottom: 1px solid #cccccc;
line-height: 50px;
}
.containerBox {
display: grid;
grid-template-columns: repeat(7, 1fr);
grid-auto-rows: 40px;
grid-gap: 1rem;
background-color: #FFFFFF;
line-height: 40px;
font-size: 16px;
div {
text-align: center;
}
.nowDay {
background: #4d89ff;
border-radius: 50%;
}
}
.today {
background-size: 95% 95%;
position: relative;
}
.otherDay {
background-size: 95% 95%;
position: relative;
color: #ccc;
}
.link {
font-size: 20px;
color: #2d8cf0;
}
.date-desc {
display: block;
position: absolute;
top: 6.8vw;
left: 1.5vw;
font-size: 2.3vw;
color: green;
}
实现的效果如图:
ps:可以直接将日历封装成组件后续直接引入
将日历和定位综合起来
html:结构
<template>
<div style="background-color:#4d89ff;height: 100%;">
<div class="container">
<header>
<div class="goBack" @click="goBack">
<van-icon name="arrow-left" size="20pt" />
</div>
<div class="title">签到</div>
</header>
</div>
<div class="rili">
<!-- 封装的日历组件 -->
<calendarCom />
<div class="daka">
<div class="box" v-if="flag" @click="qiandao">
<div class="data">{{ str }}</div>
<div class="tip">点击签到</div>
</div>
<div class="box" v-if="!flag">
<div class="suc">✔</div>
<div class="tip2">签到成功</div>
</div>
</div>
<div class="adress">
<van-icon name="location-o" />
{{ address }}
</div>
</div>
</div>
</template>
js核心代码
import { reactive, ref, onMounted } from "vue"
import { jsonp } from 'vue-jsonp'
import wx from 'weixin-js-sdk'
import { getSignature } from "@/api/request/login";
import { useRouter } from "vue-router";
import { Toast, Dialog } from 'vant';
import calendarCom from './components/calendar-com.vue'
import getwxFn from '@/utils/wx'
const router = useRouter()
const flag = ref<boolean>(true)
const address = ref<string>('获取定位中······')
const x = ref<string>('')
const y = ref<string>('')
let timer = reactive<any>(null)
let timer2 = reactive<any>(null)
let str = ref<any>('');
//点击签到
const qiandao = () => {
clearInterval(timer2)
timer = setTimeout(() => {
flag.value = false
}, 500)
}
//返回
const goBack = () => {
clearInterval(timer)
clearInterval(timer2)
router.push('/home')
}
//获取定位
let signature = () => {
wx.getLocation({
type: 'gcj02', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
success: (res: any) => {
x.value = res.longitude
y.value = res.latitude
//逆解析
jsonp(`https://apis.map.qq.com/ws/geocoder/v1/?location=${res.latitude},${res.longitude}`, {
key: '', //必填
get_poi: 1,
output: 'jsonp'
}).then((res) => {
debugger
if (res.status === 0) {
address.value = res.result.address_component.province + res.result.address_component.city + res.result.address_component.district + res.result.address_component.street + res.result.address_component.street_number
} else {
Toast.fail(res.message);
}
})
},
fail: (err: any) => {
console.log(err, 'faill')
}
})
wx.error((err: any) => {
throw new Error(err)
})
}
setTimeout(() => {
signature()
}, 1000)
onMounted(() => {
timer2 = setInterval(() => {
str.value = new Date().toTimeString().substring(0, 8)
}, 1000)
getwxFn()//这里是获取微信签名,我实现这个功能的时候封装起来了,需要看如何获取签名可看小程序官网,或翻阅之前的文章有记录下来
})
scc样式:
.container {
width: 100%;
height: 60px;
background-color: rgba(75, 139, 255, 1);
header {
width: 100%;
height: 44pt;
position: relative;
color: #fff;
.goBack {
position: absolute;
left: 20pt;
top: 50%;
transform: translateY(-50%);
}
.title {
font-size: 18pt;
height: 44pt;
line-height: 44pt;
}
}
}
//定位
:deep .van-nav-bar__content {
background-color: #2d8cf0;
}
:deep .van-nav-bar__title {
color: #fff;
}
:deep .van-nav-bar__text {
color: #fff;
}
:deep .van-nav-bar .van-icon {
color: #fff;
}
.rili {
height: 100%;
width: 100%;
background-color: #fff;
// overflow: hidden;
border-radius: 16pt 16pt 0pt 0pt;
.daka {
width: 100%;
height: 120pt;
//margin-top: 30pt;
position: relative;
.box {
width: 100pt;
height: 100pt;
border-radius: 50%;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-color: #4d89ff;
box-shadow: 0 0 16pt #4d89ff;
color: #fff;
.data {
width: 70pt;
height: 30pt;
margin-top: 36pt;
margin-left: 10pt;
font-size: 20pt;
font-weight: 500;
}
.tip {
font-size: 12pt;
}
.suc {
margin-top: 24pt;
font-size: 24pt;
}
.tip2 {
margin-top: 8pt;
}
}
}
}