提交 52acac08 authored 作者: lidongxu's avatar lidongxu

集成启动页并给打卡按钮加动画

上级 19c8ada2
......@@ -24,4 +24,15 @@ export const addAttendanceAPI = (data) => {
method: 'POST',
data: data
})
}
/**
* 考勤列表(近 7 天)
* @param {*} data
* @returns
*/
export const getAttendanceListAPI = () => {
return request({
url: '/operation/kqmx/query/roll'
})
}
\ No newline at end of file
// 应用全局配置
module.exports = {
// baseUrl: 'https://vue.ruoyi.vip/prod-api',
mapBaseUrl: 'https://api.map.baidu.com',
mapKey: 'mryaav7xyp0Z3ItwUT3oMssYAmG8sTSU',
coordtype: 'gcj02', // 坐标类型(小程序获取和百度地图 API 使用类型要对应上)
// baseUrl: 'http://192.168.100.39:8080/api',
// baseUrl: 'http://localhost:8080',
baseUrl: 'http://192.168.140.189:8080', // 局域网请求
module.exports = {
// baseUrl: 'https://vue.ruoyi.vip/prod-api',
mapBaseUrl: 'https://api.map.baidu.com',
mapKey: 'mryaav7xyp0Z3ItwUT3oMssYAmG8sTSU',
coordtype: 'gcj02', // 坐标类型(小程序获取和百度地图 API 使用类型要对应上)
baseUrl: 'http://192.168.100.39:8080',
// baseUrl: 'http://localhost:8080',
// baseUrl: 'http://192.168.140.189:8080', // 局域网请求
// 应用信息
appInfo: {
// 应用名称
......
{
"dependencies": {
"await-to-js": "^3.0.0"
}
}
{
"pages": [
{
"path": "pages/login",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/login",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/start",
"style": {
"navigationBarTitleText": "欢迎使用-小卤通"
}
},
{
"path": "pages/index",
"style": {
......@@ -24,7 +30,6 @@
"navigationBarTitleText": "工作台"
}
},
{
"path": "pages/register",
"style": {
......@@ -123,6 +128,6 @@
"navigationBarTitleText": "RuoYi",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#FFFFFF"
}
},
"entryPagePath": "pages/start"
}
\ No newline at end of file
<template>
<view class="wrap">
<div class="button_wrap">
<button v-for="obj in attTimeKeyList" :class="obj.disabled ? 'workbtn_disabled' : obj.class"
:disabled="obj.disabled" @click="attendanceClickFn(obj.type)">{{ obj.title }}</button>
<div v-for="obj in attTimeKeyList" class="btn_item"
:class="(obj.timeOut || obj.ready) ? 'workbtn_disabled' : obj.class" :disabled="obj.disabled || obj.ready"
@click="attendanceClickFn(obj.type)">
<span>{{ obj.title }}</span>
<span v-if="obj.timeOut && !obj.ready" class="over">请补卡</span>
<span v-if="obj.ready" class="right">已打卡</span>
</div>
</div>
<uni-list class="list_wrap">
<uni-list-item>
<uni-list-item v-for="item in attendanceList">
<template v-slot:header>
<span>2024-11-04 星期一</span>
<span>{{ parseTime(item.time, '{y}-{m}-{d} 星期{a}') }}</span>
</template>
<!-- 自定义 body -->
<template v-slot:body>
<div class="item_body">
<div class="item">
<text class="time">09:05:20</text>
<text class="title">上班打卡</text>
</div>
<div class="item">
<text class="time">09:05:20</text>
<text class="title">午班打卡</text>
</div>
<div class="item">
<text class="time">09:05:20</text>
<text class="title">下班打卡</text>
<div class="item" v-for="obj in item.kqTimeList">
<text class="time">{{ parseTime(obj.kqTime, '{h}:{i}:{s}') }}</text>
<text class="title">{{ attendanceDict[obj.kqType] }}打卡</text>
</div>
</div>
</template>
</uni-list-item>
<uni-list-item v-if="attendanceList.length === 0">
<template v-slot:body>
<view class="empty-box">
<text class="empty-text">暂无打卡记录</text>
</view>
</template>
</uni-list-item>
</uni-list>
</view>
</template>
<script>
import { getAttendanceDetailAPI, addAttendanceAPI } from '@/api/attendance'
import { checkStartLessEndTime } from '@/utils/common'
import { getAttendanceDetailAPI, addAttendanceAPI, getAttendanceListAPI } from '@/api/attendance'
import { checkStartLessEndTime, parseTime, isSameDay, zeroTo24 } from '@/utils/common'
export default {
data() {
return {
attTimeKeyList: []
attTimeKeyList: [], // 循环按钮配置列表
attendanceList: [], // 近 7 天考勤列表
attendanceDict: { // 考勤字典
1: '上班',
2: '午班',
3: '下班'
}
}
},
created() {
this.getRuleInfo()
this.getRuleInfoFn()
},
methods: {
parseTime,
// 获取考勤规则详情
async getRuleInfo() {
async getRuleInfoFn() {
const list = [{ // 需要考勤列表
beginKey: 'firBegintime',
beginValue: '',
......@@ -54,8 +66,9 @@ export default {
endValue: '',
class: 'go_work',
title: '上班打卡',
disabled: false, // 是否可以交互
type: 'begin', // 考勤类型
timeOut: false, // 是否过了打卡时间
ready: false, // 是否已经打过卡了
type: 1, // 考勤类型
},
{
beginKey: 'secBegintime',
......@@ -64,8 +77,9 @@ export default {
endValue: '',
class: 'after_work',
title: '午班打卡',
disabled: false,
type: 'after',
timeOut: false,
ready: false,
type: 2,
},
{
beginKey: 'thiBegintime',
......@@ -74,8 +88,9 @@ export default {
endValue: '',
class: 'off_work',
title: '下班打卡',
disabled: false,
type: 'off',
timeOut: false,
ready: false,
type: 3,
}]
// 需要提取打卡的时间
const { data } = await getAttendanceDetailAPI(this.$store.getters.user.ruleId)
......@@ -90,18 +105,15 @@ export default {
obj['beginValue'] = data[obj.beginKey]
obj['endValue'] = data[obj.endKey]
// 判断服务器时间
obj['disabled'] = checkStartLessEndTime(data[obj.endKey], data.currently)
obj['timeOut'] = checkStartLessEndTime(zeroTo24(data[obj.endKey]), data.currently)
}
this.attTimeKeyList = list
// 考勤打卡记录
this.getAttendanceListFn()
},
// 考勤打卡
async attendanceClickFn(type) {
const attendanceType = {
'begin': 1,
'after': 2,
'off': 3
// 前端考勤类型,对应后端数字代表考勤类型
}
const { addressComponent, location } = await this.$store.getters.location
const { lng, lat } = location
const { province, city, district, street } = addressComponent
......@@ -115,8 +127,45 @@ export default {
"kqCounty": district,
"kqAddress": street,
"kqPicurl": "https://www.baidu.com",
"kqType": attendanceType[type]
"kqType": type
})
// 考勤打卡记录
this.getAttendanceListFn()
},
// 获取考勤列表
async getAttendanceListFn() {
const { data: { current, data } } = await getAttendanceListAPI()
const list = []
// 如果有打卡时间,则从后台获取结构数据
if (Object.keys(data).length > 0) {
Object.entries(data).forEach(([key, value]) => {
const attTime = [] // 打卡时间
value.forEach(obj => {
attTime.push({
kqTime: obj.kqTime,
kqType: obj.kqType
})
})
list.push({
time: key,
kqTimeList: attTime
})
})
this.attendanceList = list
// 判断有没有今日的打卡记录
if (isSameDay(this.attendanceList[0].time, current)) {
// 根据记录决定今日哪个按钮应该关闭
this.attTimeKeyList.forEach((obj, index) => {
// 从按钮数组里去已打卡时间数组里取出,打卡时间对象
const readyObj = this.attendanceList[0].kqTimeList[index]
if (readyObj) {
// 如果找到了,证明已经打过卡了
obj.ready = true
}
})
}
}
}
}
}
......@@ -136,40 +185,92 @@ export default {
align-items: center;
gap: 50rpx;
padding: 50rpx 0;
flex: 1;
.go_work,
.after_work,
.off_work,
.workbtn_disabled {
width: 250rpx;
height: 250rpx;
border-radius: 50%;
.btn_item {
display: flex;
flex-direction: column;
justify-content: center;
gap: 0 !important;
width: 250rpx;
height: 250rpx;
border-radius: 50%;
align-items: center;
color: white;
}
transition: all 0.1s linear;
position: relative;
.workbtn_disabled {
background-color: #c7c8c8;
}
.over {
font-size: 28rpx;
color: #d9322c;
margin-top: 10rpx;
}
.go_work {
background-color: #32cdbf;
}
.right{
font-size: 28rpx;
color: #32cdbf;
margin-top: 10rpx;
}
.after_work {
background-color: #e0bd47;
}
&.workbtn_disabled {
background-color: #c7c8c8;
color: rgb(160, 156, 156);
}
&.go_work {
background-color: #32cdbf;
}
&.after_work {
background-color: #e0bd47;
}
&.off_work {
background-color: #5dabed;
}
.off_work {
background-color: #5dabed;
// 打卡按钮动画
&.go_work,
&.after_work, &.off_work {
&::before,
&::after {
content: ' ';
width: 250rpx;
height: 250rpx;
border-radius: 50%;
position: absolute;
animation: eff68 1.4s linear infinite;
}
&::after {
animation-delay: 0.7s;
}
@keyframes eff68 {
0% {
box-shadow: 0 0 0 0px #0093E9;
opacity: 0.2;
}
100% {
box-shadow: 0 0 0 20px #0093E9;
opacity: 0;
}
}
&:active {
transform: scale(0.8);
}
}
}
}
.list_wrap {
height: 700rpx;
overflow-y: scroll;
::v-deep .uni-list-item__container {
display: block !important;
}
......@@ -194,4 +295,4 @@ export default {
}
}
}
</style>
</style>
\ No newline at end of file
......@@ -70,7 +70,11 @@ export default {
onLoad: function () { },
methods: {
async addendanceClick() {
if (!this.$store.getters.user.ruleId) {
return this.$modal.msgError("您无需打卡");
}
const result = await this.$store.getters.location
console.log('首页-获取地理位置', result)
// 判断用户位置和获取位置-城市是否一致
if (result.addressComponent.city.startsWith(this.$store.getters.user.workCityName)) {
// 跳转到打卡页面
......
......@@ -17,7 +17,7 @@
<view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled">
<view class="iconfont icon-code icon"></view>
<input v-model="loginForm.code" type="number" class="input" placeholder="请输入验证码" maxlength="4" />
<view class="login-code">
<view class="login-code">
<image :src="codeUrl" @click="getCode" class="login-code-img"></image>
</view>
</view>
......@@ -34,172 +34,175 @@
<text @click="handlePrivacy" class="text-blue">《隐私协议》</text>
</view>
</view>
</view>
</template>
<script>
import { getCodeImg } from '@/api/login'
export default {
data() {
return {
codeUrl: "",
captchaEnabled: false,
// 用户注册开关
register: false,
globalConfig: getApp().globalData.config,
loginForm: {
username: "testlql01",
password: "123456",
// code: "",
uuid: ''
}
import { getCodeImg } from '@/api/login'
export default {
data() {
return {
codeUrl: "",
captchaEnabled: false,
// 用户注册开关
register: false,
globalConfig: getApp().globalData.config,
loginForm: {
username: "wangxiaolu_csjl",
password: "123456",
// code: "",
uuid: ''
}
}
},
created() {
// this.getCode()
},
methods: {
// 用户注册
handleUserRegister() {
this.$tab.redirectTo(`/pages/register`)
},
// 隐私协议
handlePrivacy() {
let site = this.globalConfig.appInfo.agreements[0]
this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
},
created() {
// this.getCode()
// 用户协议
handleUserAgrement() {
let site = this.globalConfig.appInfo.agreements[1]
this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
},
methods: {
// 用户注册
handleUserRegister() {
this.$tab.redirectTo(`/pages/register`)
},
// 隐私协议
handlePrivacy() {
let site = this.globalConfig.appInfo.agreements[0]
this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
},
// 用户协议
handleUserAgrement() {
let site = this.globalConfig.appInfo.agreements[1]
this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
},
// 获取图形验证码
getCode() {
getCodeImg().then(res => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
if (this.captchaEnabled) {
this.codeUrl = 'data:image/gif;base64,' + res.img
this.loginForm.uuid = res.uuid
}
})
},
// 登录方法
async handleLogin() {
if (this.loginForm.username === "") {
this.$modal.msgError("请输入您的账号")
} else if (this.loginForm.password === "") {
this.$modal.msgError("请输入您的密码")
}
// else if (this.loginForm.code === "" && this.captchaEnabled) {
// this.$modal.msgError("请输入验证码")
// }
else {
this.$modal.loading("登录中,请耐心等待...")
this.pwdLogin()
// 获取图形验证码
getCode() {
getCodeImg().then(res => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
if (this.captchaEnabled) {
this.codeUrl = 'data:image/gif;base64,' + res.img
this.loginForm.uuid = res.uuid
}
},
// 密码登录
async pwdLogin() {
this.$store.dispatch('Login', this.loginForm).then(() => {
this.$modal.closeLoading()
this.loginSuccess()
}).catch(() => {
if (this.captchaEnabled) {
this.getCode()
}
})
},
// 登录成功后,处理函数
loginSuccess(result) {
// 设置用户信息
this.$store.dispatch('GetInfo').then(res => {
this.$tab.reLaunch('/pages/index')
})
})
},
// 登录方法
async handleLogin() {
if (this.loginForm.username === "") {
this.$modal.msgError("请输入您的账号")
} else if (this.loginForm.password === "") {
this.$modal.msgError("请输入您的密码")
}
// else if (this.loginForm.code === "" && this.captchaEnabled) {
// this.$modal.msgError("请输入验证码")
// }
else {
this.$modal.loading("登录中,请耐心等待...")
this.pwdLogin()
}
},
// 密码登录
async pwdLogin() {
this.$store.dispatch('Login', this.loginForm).then(() => {
this.$modal.closeLoading()
this.loginSuccess()
}).catch(() => {
if (this.captchaEnabled) {
this.getCode()
}
})
},
// 登录成功后,处理函数
async loginSuccess(result) {
// 设置用户信息
await this.$store.dispatch('GetInfo')
await this.getAuth()
this.$tab.reLaunch('/pages/index')
},
// 登录成功后,开始获取各种权限
async getAuth() {
// 获取地址位置
return this.$store.dispatch('GetLocation')
}
}
}
</script>
<style lang="scss">
page {
background-color: #ffffff;
}
page {
background-color: #ffffff;
}
.normal-login-container {
width: 100%;
.normal-login-container {
.logo-content {
width: 100%;
font-size: 21px;
text-align: center;
padding-top: 15%;
.logo-content {
width: 100%;
font-size: 21px;
text-align: center;
padding-top: 15%;
image {
border-radius: 4px;
}
image {
border-radius: 4px;
}
.title {
margin-left: 10px;
}
}
.title {
.login-form-content {
text-align: center;
margin: 20px auto;
margin-top: 15%;
width: 80%;
.input-item {
margin: 20px auto;
background-color: #f5f6f7;
height: 45px;
border-radius: 20px;
.icon {
font-size: 38rpx;
margin-left: 10px;
color: #999;
}
.input {
width: 100%;
font-size: 14px;
line-height: 20px;
text-align: left;
padding-left: 15px;
}
}
.login-form-content {
text-align: center;
margin: 20px auto;
margin-top: 15%;
width: 80%;
.input-item {
margin: 20px auto;
background-color: #f5f6f7;
height: 45px;
border-radius: 20px;
.icon {
font-size: 38rpx;
margin-left: 10px;
color: #999;
}
.login-btn {
margin-top: 40px;
height: 45px;
}
.input {
width: 100%;
font-size: 14px;
line-height: 20px;
text-align: left;
padding-left: 15px;
}
.reg {
margin-top: 15px;
}
}
.xieyi {
color: #333;
margin-top: 20px;
}
.login-btn {
margin-top: 40px;
height: 45px;
}
.reg {
margin-top: 15px;
}
.xieyi {
color: #333;
margin-top: 20px;
}
.login-code {
.login-code {
height: 38px;
float: right;
.login-code-img {
height: 38px;
float: right;
.login-code-img {
height: 38px;
position: absolute;
margin-left: 10px;
width: 200rpx;
}
position: absolute;
margin-left: 10px;
width: 200rpx;
}
}
}
}
</style>
<template>
<image src="@/static/start.jpg" alt="" class="start_img"/>
</template>
<script>
export default {
mounted(){
setTimeout(() => {
this.$tab.redirectTo('pages/login')
}, 2500)
}
}
</script>
<style scoped>
.start_img{
width: 100%;
height: 100vh;
}
</style>
......@@ -29,7 +29,6 @@ list.forEach(item => {
if (checkWhite(to.url)) {
return true
}
console.log(to.url)
uni.reLaunch({ url: loginPage })
return false
}
......
差异被折叠。
import storage from '@/utils/storage'
import constant from '@/utils/constant'
import { reverseGeocoding } from '@/api/common'
import $modal from '@/plugins/modal'
const location = {
state: {
......@@ -29,9 +30,12 @@ const location = {
location: res.latitude + "," + res.longitude
}
})
console.log('2', result)
commit('SET_LOCATION', result)
resolve(result)
},
fail: (err) => {
$modal.msgError("请允许获取位置权限");
reject(err)
}
})
})
......
......@@ -66,4 +66,67 @@ export function checkStartLessEndTime(startTime, endTime) {
endDate.setMinutes(endTime.split(":")[1])
endDate.setSeconds(endTime.split(":")[2])
return startDate.getTime() - endDate.getTime() < 0
}
// 日期格式化
export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) {
return null
}
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
time = parseInt(time)
} else if (typeof time === 'string') {
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
// 判断 2 个日期是否为同一天
export function isSameDay(date1, date2) {
if (!date1 || !date2) return false
const date1Year = new Date(date1).getFullYear()
const date1Month = new Date(date1).getMonth() + 1
const date1Date = new Date(date1).getDate()
const date2Year = new Date(date2).getFullYear()
const date2Month = new Date(date2).getMonth() + 1
const date2Date = new Date(date2).getDate()
return date1Date === date2Date && date1Month === date2Month && date1Year === date2Year
}
// 把 00:00:00 转成 24:00:00
export const zeroTo24 = (time) => {
if (time === '00:00:00') {
return '24:00:00'
} else {
return time
}
}
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论