1:建表SQL
CREATE TABLE `o_sign` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'id',
`shop_id` int DEFAULT NULL COMMENT '商家id',
`username` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户名称',
`location` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '打卡位置',
`daka_time` timestamp NULL DEFAULT NULL COMMENT '打卡时间',
`comments` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
`isdeleted` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '0:正常数据 1:逻辑删除',
`clocking_records` int DEFAULT NULL COMMENT '记录打卡次数',
`integral` int DEFAULT NULL COMMENT '打卡积分',
`update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
百度开放平台
申请key:
<script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=xxxxxxxxxxxxxxxxxxx"></script>
后台打卡逻辑(可以根据自己的具体逻辑进行修改)
@ApiOperation("定位打卡列表")
@RepeatSubmit(interval = 2000, message = "接口请求过于频繁")
@PostMapping("positioning")
public Result getOrderList(@RequestBody Sign sign){
//拿到商家id
String username = sign.getUsername();
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername,username);
User user = userService.getOne(wrapper);
Integer userId = user.getId();
Integer shopId = shopService.getShopId(userId);
Result result = signService.saveOrUpdate(shopId, sign);
return result;
}
@Override
public Result saveOrUpdate(Integer shopId, Sign sign) {
Optional.ofNullable(shopId).orElseThrow(() -> new RuntimeException("shopId is null"));
//获取当前时间
String today = DateUtil.today();
Date dateTime = DateUtil.parse(today, "yyyy-MM-dd");
LambdaQueryWrapper<Sign> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Sign::getShopId,shopId);
Sign selectOne = signMapper.selectOne(wrapper);
Sign signMessage = new Sign();
BeanUtil.copyProperties(sign, signMessage);
//一次加20积分,可以低效xxx钱,暂时还在思考中;
signMessage.setIntegral(INTEGRAL);
Integer count = 0;
if (selectOne != null) {
//获取上次的打卡时间
Date dakaTime = selectOne.getDakaTime();
long days = DateUtil.betweenDay(dateTime, dakaTime, true);
if(days < 1) {
// 时间间距小于1天
return Result.fail(Integer.parseInt("500"),"不能一天打卡2次");
} else {
// 时间间距大于1天
Integer clockingRecords = selectOne.getClockingRecords();
clockingRecords = clockingRecords+1;
Integer integral = selectOne.getIntegral();
integral = integral+20;
String comments = selectOne.getComments();
signMessage.setComments(comments);
signMessage.setIntegral(integral);
signMessage.setClockingRecords(clockingRecords);
signMessage.setDakaTime(dateTime);
signMessage.setId(selectOne.getId());
signMessage.setShopId(shopId);
signMessage.setUpdateTime(dateTime);
//走更新 根据商家ID进行修改
count = signMapper.updateById(signMessage);
boolean b = count > 0 ? true : false;
return Result.success(b);
}
}
//走新增
signMessage.setShopId(shopId);
signMessage.setClockingRecords(1);
signMessage.setIsdeleted(IS_DELETED);
signMessage.setDakaTime(dateTime);
signMessage.setUpdateTime(dateTime);
count = signMapper.insert(signMessage);
boolean b = count > 0 ? true : false;
return Result.success(b);
}
@Override
public Integer getShopId(Integer userId) {
Shop shop = shopMapper.selectById(userId);
return shop.getId();
}
前台在登录的时候,登录获取位置
mounted() {
// 获取地理位置
var geolocation = new BMapGL.Geolocation()
// 判断浏览器类型
var ua = navigator.userAgent.toLowerCase()
var isFirefox = ua.indexOf('firefox') !== -1
var isChrome = ua.indexOf('chrome') !== -1 && ua.indexOf('safari') !== -1
geolocation.getCurrentPosition(function(r) {
if (this.getStatus() === BMAP_STATUS_SUCCESS) {
const province = r.address.province
let city = r.address.city
const district = r.address.district
const street = r.address.street
// 处理火狐浏览器返回的地理位置信息格式
if (isFirefox) {
city = r.address.city
}
// 处理谷歌浏览器返回的地理位置信息格式
if (isChrome) {
if (city === '市辖区' || city === '县') {
city = r.address.province + r.address.district
}
}
console.log('=======', province)
console.log('=======', city)
console.log('=======', district)
console.log('=======', street)
// alert(province + city + district + street)
localStorage.setItem('location', province.trim() + ' ' + city.trim() + ' ' + district.trim() + ' ' + street.trim())
}
})
},
前台主页打卡功能页面:
<template>
<div style="color: #666;font-size: 14px;">
<div style="padding-bottom: 20px">
<b>您好!{{ user }}</b>
</div>
<el-card shadow="hover">
欢迎使用本系统
<el-divider shadow="hover" />
虎虎生威,虎年大吉
</el-card>
<el-card shadow="hover" style="margin-top: 20px;width: 300px">
<div style="margin: 20px 0; font-size: 15px;color: #0d8d84;cursor: pointer">当前系统时间:{{ new Date().getFullYear() + '年' + (new Date().getMonth() + 1) + '月' + new Date().getDate() + '日' }}</div>
<div
style="width: 100px; height: 100px; line-height: 100px; border-radius: 50%; background-color: #1E90FF;
font-size: 25px; color: #fff; text-align: center; cursor: pointer; box-shadow: 0 0 30px rgba(0, 0, 0, .2);"
type="button"
@click="sign"
>
打 卡
</div>
</el-card>
</div>
</template>
<script>
import { reqPositioning } from '@/api/sign/sign'
export default {
name: 'Daka',
data() {
return {
user: localStorage.getItem('user') ? localStorage.getItem('user') : {},
comments: ''
}
},
methods: {
async sign() {
this.$confirm('你今天打卡了吗, 是否继续?', '定位打卡可获得20积分', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async() => {
const location = localStorage.getItem('location')
// const password = localStorage.getItem('password')
const username = this.user
console.log(username)
const sign = {
username: username,
location: location,
comments: this.comments
}
const res = await this.$API.sign.reqPositioning(sign)
if (res.code === 200) {
this.$message({
type: 'success',
message: '已打卡成功'
})
}
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消打卡'
})
})
}
}
}
</script>
<style scoped>
</style>
效果图:
管理员端后端:(分页,模糊查询)
@ApiOperation("打卡详情信息列表")
@PostMapping("messageByPage")
public Result messageByPage(@RequestBody Map<String, Object> requestBodyMaps){
//拿到商家id
return signService.selectOrderListByPage(requestBodyMaps);
}
@Override
public Result selectOrderListByPage(Map<String, Object> requestBodyMaps) {
Integer pageCount = (Integer) requestBodyMaps.get("page");
Integer pageSize = (Integer) requestBodyMaps.get("pageSize");
String username = (String) requestBodyMaps.get("username");
String location = (String) requestBodyMaps.get("location");
String comments = (String) requestBodyMaps.get("comments");
LambdaQueryWrapper<Sign> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//如果姓名不为 null 不为空,模糊查询
if (StrUtil.isNotBlank(username)) {
lambdaQueryWrapper.like(Sign::getUsername, "%" + username + "%");
}
//如果定位不为空,模糊查询定位信息
if (StrUtil.isNotBlank(location)) {
lambdaQueryWrapper.like(Sign::getLocation, "%" + location + "%");
}
//如果评论不为空,查询评论信息
if (StrUtil.isNotBlank(comments)) {
lambdaQueryWrapper.eq(Sign::getComments, comments);
}
lambdaQueryWrapper.eq(Sign::getIsdeleted,"0");
signMapper.selectList(lambdaQueryWrapper);
// 查询第pageCount页,每页pageSize条记录
Page<Sign> page = new Page<>(pageCount, pageSize);
IPage<Sign> sighPage = signMapper.selectPage(page, lambdaQueryWrapper);
// 当前页的记录列表
List<Sign> sighList = sighPage.getRecords();
// 总记录数
long total = sighPage.getTotal();
SighListVo sighListVo = new SighListVo();
sighListVo.setSighVoList(sighList);
sighListVo.setTotal(total);
return Result.success(sighListVo);
}
}
管理员端前端:
<template>
<div>
<div style="margin-bottom: 20px;">
<el-card shadow="hover" body-style="width:100%;">
<div style="display: flex">
<div style="display: flex;align-items:center;margin-right:20px">
<i class="el-icon-user" />
</div>
<div style="display: flex;align-items:center;margin-right:10px">商家名称:</div>
<el-input v-model="comment.username" style="width: 200px" placeholder="请输入用户名" clearable />
<div style="display: flex;align-items:center;margin-left:20px;">
<i class="el-icon-location-outline" />
</div>
<div style="display: flex;align-items:center;margin-left:30px;margin-right:10px ">打卡位置:</div>
<el-input v-model="comment.location" style="width: 200px" placeholder="请输入地址" clearable />
<div style="display: flex;align-items:center;margin-left:20px;">
<i class="el-icon-loading" />
</div>
<div style="display: flex;align-items:center;margin-left:20px;margin-right:10px">频率:</div>
<!-- <el-input v-model="phone" style="width: 200px" placeholder="请输入频率" clearable />-->
<el-select v-model="comment.comments" placeholder="请选择" clearable>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<div style="padding: 0px 20px;margin-left: 50px">
<el-button type="primary" icon="el-icon-search" @click="load">查找</el-button>
</div>
<div>
<el-button type="primary" plain icon="el-icon-info" @click="clearInput">清空</el-button>
</div>
</div>
</el-card>
</div>
<el-card shadow="hover">
<el-table :data="sighList" border stripe :header-cell-class-name="'headerBg'" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" sortable />
<el-table-column prop="username" label="商家名称" align="center" />
<el-table-column prop="location" label="打卡位置" />
<el-table-column prop="dakaTime" label="打卡时间" align="center" />
<el-table-column prop="comments" label="打卡频率" align="center" />
<el-table-column prop="clockingRecords" label="打卡次数" align="center" />
<el-table-column prop="integral" label="打卡积分" align="center" />
<el-table-column label="操作" width="300" align="center">
<template slot-scope="scope">
<el-button style="margin-right: 20px" type="success" @click="handleEdit(scope.row)">编辑 <i class="el-icon-edit" /></el-button>
<el-popconfirm
class="ml-5"
confirm-button-text="确定"
cancel-button-text="我再想想"
icon="el-icon-info"
icon-color="red"
title="您确定删除吗?"
@confirm="deleteById(scope.row)"
>
<el-button
slot="reference"
style="width: 80px"
type="danger"
>删除 <i class="el-icon-remove-outline" /></el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div style="padding: 10px 0">
<el-pagination
:current-page="pageParams.page"
:page-sizes="[2, 5, 10, 20]"
:page-size="pageParams.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pageParams.page"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<el-dialog title="信息" :visible.sync="dialogFormVisible" width="40%" :close-on-click-modal="false">
<el-form label-width="140px" size="small" style="width: 85%;">
<el-form-item label="商家名称">
<el-input v-model="form.username" autocomplete="off" disabled />
</el-form-item>
<el-form-item label="打卡位置">
<el-input v-model="form.location" autocomplete="off" disabled />
</el-form-item>
<el-form-item label="打卡时间">
<el-date-picker v-model="form.dakaTime" disabled type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择日期时间" />
</el-form-item>
<el-form-item label="打卡次数">
<el-input v-model="form.clockingRecords" autocomplete="off" disabled />
</el-form-item>
<el-form-item label="打卡积分">
<el-input v-model="form.integral" autocomplete="off" disabled />
</el-form-item>
<el-form-item label="打卡频率">
<el-select v-model="form.comments" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
</el-card>
</div>
</template>
<script>
export default {
name: 'Sign',
data() {
return {
options: [{
value: '1',
label: '一般'
}, {
value: '2',
label: '中等'
}, {
value: '3',
label: '优秀'
}],
value: '',
tableData: {},
sighList: [],
// 分页参数
pageParams: {
page: 1,
pageSize: 10,
total: 0
},
comment: {
username: '',
location: '',
comments: ''
},
name: '',
form: {},
dialogFormVisible: false,
multipleSelection: [],
user: localStorage.getItem('user') ? localStorage.getItem('user') : {}
}
},
mounted() {
this.load()
},
methods: {
// 删除这条信息
async deleteById(row) {
this.$confirm('删除这条打开信息后后无法回撤, 是否继续?', '删除评论', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async() => {
const res = await this.$API.sign.reqdeleteById(row)
if (res.code === 200) {
this.$message.success('删除成功')
this.load()
}
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
this.load()
})
},
clearInput() {
this.comment.username = ''
this.comment.location = ''
this.comment.comments = ''
this.load()
},
async load() {
const selectedOption = this.options.find(option => option.value === this.comment.comments)
const selectedLabel = selectedOption ? selectedOption.label : ''
console.log(selectedLabel)
console.log(this.comment)
this.comment.comments = selectedLabel
const res = await this.$API.sign.reqmessageByPage(this.pageParams, this.comment)
if (res.code === 200) {
this.pageParams.total = res.data.total
this.sighList = res.data.sighVoList
}
},
async save() {
const selectedOption = this.options.find(option => option.value === this.form.comments)
const selectedLabel = selectedOption ? selectedOption.label : this.form.comments
console.log(selectedLabel)
console.log(this.tableData)
this.tableData.comments = selectedLabel
const res = await this.$API.sign.reqUpdateMessage({ ...this.tableData })
if (res.code === 200) {
this.$message({
type: 'success',
message: '保存成功'
})
this.dialogFormVisible = false
this.load()
}
},
handleEdit(row) {
this.form = JSON.parse(JSON.stringify(row))
console.log(this.form)
this.tableData = row
this.dialogFormVisible = true
},
handleSelectionChange(val) {
console.log(val)
this.multipleSelection = val
},
handleSizeChange(pageSize) {
console.log(pageSize)
this.pageParams.pageSize = pageSize
this.load()
},
handleCurrentChange(pageNum) {
console.log(pageNum)
this.pageNum = pageNum
this.load()
}
}
}
</script>
<style scoped>
.headerBg {
background: #eee!important;
}
</style>
效果图: