引言
领导:小林,你之前有接触过app或h5开发吗?
我:H5的话应该就前端开发,这个懂一些,app的话大学学过一点,但是工作没涉及到。
领导:我是想把企微上的这个查询考勤页面的界面重新做一下,你看看要怎么做,这个我们有源代码,不过是用php的。
我:这个可以研究看看哈哈哈
看了一下代码,发现前端的代码是被打包过的,完全是不能用~~~
我:领导,那个系统的代码我发现,里面是没有前端的源码的,它里面的html文件都被打包过,我们是修改不了的。
领导:那你就不修改吧,可以直接按现在的模式重新开发一个吧,毕竟之前的是用php开发的,后面都转到java开发吧。
我:这个就很困难了,他这个前端代码都是打包好了,完全是看不懂的,都不知道那个界面调用的接口是哪一个,而且这个框架底层就是用php,改语言相当于重构他的这个系统,这个我感觉我不太行,PHP之前也没做过说实话这代码都看不懂。
领导:我的想法是重新做一个插件挂到企微上,等大家可以看自己的考勤数据而已,界面可以用其它方式,例如h5或小程序,界面参考之前的而已。调用接口其实没有接口,估计都是直接读取人事系统的数据库资料。
我:哦哦哦,那我明白了。
后面我便采用springboot+vue+elementui搭建了一个查询考勤的页面,并且集成到了企业微信当中去,以下是操作的步骤和代码:
一、前端代码实现
前端使用的vue+elementui框架,整合thymeleaf模版引擎来于后台进行交互,日历使用了elementui的calenda组件(Element - The world's most popular Vue UI framework),官方对于这个组件的介绍是比较简单的,对于没有前端开发经验的我来说,咱也是花费了好长的时间来研究一些样式啥的,我做的改动的也不是很大,基本满足要求,下面我们对比一下:
官方提供的:
自己最终实现的:
PC效果:
移动效果:
实现代码:
<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Element UI 的 CSS 文件 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
</head>
<style lang='scss' scoped>
.circle-number {
display: inline-block;
width: 15px; /* 调整圆的大小 */
height: 15px; /* 调整圆的大小 */
line-height: 15px; /* 使文字垂直居中 */
text-align: center; /* 使文字水平居中 */
border-radius: 50%; /* 使元素变为圆形 */
background-color: #0cccec; /* 背景颜色 */
color: #fff; /* 文字颜色 */
font-size: 12px; /* 文字大小 */
font-weight: bold; /* 文字粗细 */
margin-right: 5px;
}
.time-info {
display: flex;
align-items: center;
}
.container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
}
.dksj {
font-size: 18px; /* 文字大小 */
font-weight: bold; /* 文字粗细 */
}
.DateBox {
width: 100%;
height: 100%;
/* 确保没有内边距、边框和外边距会影响大小 */
padding: 0;
border: none;
margin: 0;
box-sizing: border-box;
font-size: 20px;
font-family: 楷体;
border-radius: 50%;
display: flex; /* 使用 flex 布局 */
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
.el-calendar-table__row td {
border: none !important;
}
.el-calendar-table .el-calendar-day {
height: 41px;
text-align: center;
border-radius: 50%;
padding: 1px;
}
.highlight {
text-align: center;
font-size: 20px; /* 文字大小 */
font-family: 楷体;
background-color: #ea0909;
color: #ffffff;
}
/*隐藏今天按钮*/
.el-button-group>.el-button:not(:first-child):not(:last-child){
display: none;
}
.el-calendar-table .el-calendar-day:hover {
color: #ffffff;
background-color: #079ff1;
}
</style>
<body>
<div id="app" >
<div class="calendar">
<el-card>
<el-calendar >
<!-- 这里使用的是 2.5 slot 语法,对于新项目请使用 2.6 slot 语法-->
<template
slot="dateCell"
slot-scope="{date, data}" >
<div @click="choose(data)" :class="{'highlight': isHighlighted(data.day)}"
class="DateBox">
{{ data.day.split('-').slice(2).join('-') }}
</div>
</template>
</el-calendar>
</el-card>
</div>
<el-divider></el-divider>
<el-card class="box-card">
<p style="text-align: left;font-size: 15px;font-weight: bold;font-family: 楷体;">
{{bc}}</p><p style="text-align: left;color: red;font-size: 15px;font-weight: bold;font-family: 楷体;">{{kq}}</p>
<div v-for="(item,index) in items" class="text item">
<div class="container">
<div>
<span class="circle-number">{{index + 1}}</span>
打卡时间
</div>
<div class="time-info">
<span class="dksj">{{item.message}}</span>
</div>
</div>
</div>
</el-card>
</div>
<!-- 引入 Vue 和 Element UI 的 JavaScript 文件 -->
<script src="/vue/vue.js"></script>
<script src="/vue/elementui.js"></script>
<script src="/vue/axios.js"></script>
<script th:inline="javascript">
var loading
// var URL = "http://kqtest.goldensea.com";
var URL = '';
// 创建 Vue 实例并挂载到 #app 元素上
var _this = new Vue({
el: '#app',
data: {
items: [],
bc: null,
kq: null,
highlightedDates: []
},
created() {
loading = Vue.prototype.$loading({
lock: true,
text: '加载中',
target: 'html',
background: 'rgba(0, 0, 0, 0.2)'
})
// config 正确的情况下会执行 ready 中的代码
//debugger;
var userNo = [[${userNo}]];
var dateNew = new Date();
var year = dateNew.getFullYear();
var month = dateNew.getMonth() + 1;
month = month < 10 ? '0' + month : month;
var day = dateNew.getDate();
day = day < 10 ? '0' + day : day;
var formattedDate = year + '-' + month + '-' + day;
var data = {
userNo: userNo,
date: formattedDate
};
axios.post(URL+'/api/getAttendance',data).then(
response=>{
if (response.data && response.data.code == 200) {
_this.items = [];
_this.bc = null;
_this.kq = null;
var dk = response.data.data.arry
var bc = response.data.data.shift //班次
_this.bc = bc;
for (var i = 0; i < dk.length; i++) {
var a = dk[i];
_this.items.push({message: dk[i]});
}
} else{
if( response.data.code == "000"){
this.$message.error("查询报错:"+response.data.msg+";请联系管理员处理!!!");
}else {
this.$message.error("查询无数据~ 如有异常请联系管理员处理!!!");
}
}
}
);
loading.close()
},
methods: {
choose(data) {
loading = Vue.prototype.$loading({
lock: true,
text: '加载中',
target: 'html',
background: 'rgba(0, 0, 0, 0.2)'
})
//debugger;
var userNo = [[${userNo}]];
var day = data.day
console.log(day)
// var daystr = data.day.split("-")
var data = {
userNo: userNo,
date: day
};
axios.post(URL+'/api/getAttendance',data)
.then(
response=>{
//debugger;
if (response.data && response.data.code == 200) {
_this.items = [];
_this.bc = null;
_this.kq = null;
var dk = response.data.data.arry
var bc = response.data.data.shift //班次
var kq = response.data.data.absenteeism //旷工时长
if(kq != null){
_this.kq = "旷工"+kq+"小时!!!"
}
_this.bc = bc;
for (var i = 0; i < dk.length; i++) {
_this.items.push({message: dk[i]});
}
} else{
if( response.data.code == "000"){
this.$message.error("查询报错:"+response.data.msg+";请联系管理员处理!!!");
}else {
this.$message.error("查询无数据~ 如有异常请联系管理员处理!!!");
}
}
}
)
loading.close()
},
isHighlighted(dateStr) {
const date = new Date(dateStr);
return this.highlightedDates.some(highlightedDate => {
return highlightedDate.getFullYear() === date.getFullYear() &&
highlightedDate.getMonth() === date.getMonth()+1 &&
highlightedDate.getDate() === date.getDate();
});
},
// 假设这个方法用于从后台获取高亮日期的数据,并在获取后更新 highlightedDates
fetchHighlightedDates() {
//查询考勤异常日期
var userNo = [[${userNo}]];
var data = {
userNo: userNo
}
axios.post(URL+'/api/getAttendanceError',data).then(
response=>{
if (response.data && response.data.code == 200) {
//debugger;
var highlightedDates=new Array();
var qq = response.data.data.arry; //考勤异常日期
for (var k = 0; k < qq.length; k++){
var a = qq[k];
var a1 = qq[k].substring(0,4);
var a2 = qq[k].substring(5,7);
var a3 = qq[k].substring(8,10);
highlightedDates.push(new Date(a1,a2,a3));
}
this.highlightedDates = highlightedDates; // 假设这些是后台返回的高亮日期
} else{
if( response.data.code == "000"){
this.$message.error("查询报错:"+response.data.msg+";请联系管理员处理!!!");
}else {
this.$message.error("查询无数据~ 如有异常请联系管理员处理!!!");
}
}
}
)
}
},
mounted() {
this.fetchHighlightedDates(); // 在组件挂载后获取高亮日期数据
}
});
</script>
</body>
</html>
二、后端代码实现
后端采用springboot框架进行开发,主要是做调用获取企微的接口、查询考勤数据等逻辑。
1、企微代码
处理前端调用的路径,获取企业用户的工号信息,企微详细API文档:开发前必读 - 接口文档 - 企业微信开发者中心
Controller代码
import com.alibaba.fastjson.JSONObject;
import com.goldensea.kqhy.service.IndexService;
import com.goldensea.kqhy.util.WeComUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import java.io.IOException;
//访问页面路径
@Controller
@Api(value = "页面跳转接口", tags = {"页面接口"})
public class IndexController {
private static final Logger log = LoggerFactory.getLogger(IndexController.class);
@Autowired
private IndexService indexService;
@Autowired
private WeComUtil weComUtil;
@ApiOperation("跳转主页")
@GetMapping("/index")
public String indexfist() {
return "index";
}
@ApiOperation("通过工号查询名字后返回到考勤查看页面")
@GetMapping("calendar/indexkq")
public String indexforUserNo(@RequestParam("code") String code, Model model) {
log.info("code****:"+code);
//通过工号查询名字
JSONObject json = indexService.selectNameByUserNo(code);
model.addAttribute("userNo",json.get("userNo"));
model.addAttribute("name",json.get("name"));
return "calendar/index";
}
@ApiOperation("企微进入考勤查看页面,经过处理跳转到通过工号查询考勤查看页面")
@GetMapping("calendar/indexInfo")
public String index(Model model) {
try {
String requestUrl = null;
//通过企业微信接口获取用户信息
requestUrl = indexService.weComGetUserNo();
model.addAttribute("requestUrl",requestUrl);
return "redirect:" + requestUrl;
}catch (Exception e){
model.addAttribute("error",e.getMessage());
return "kqerror/500";
}
}
}
Service代码
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.goldensea.kqhy.service.IndexService;
import com.goldensea.kqhy.util.WeComUtil;
import com.goldensea.kqhy.util.YmlConfig;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.thymeleaf.util.StringUtils;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
public class IndexServiceImpl implements IndexService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private YmlConfig ymlConfig;
@Autowired
private WeComUtil weComUtil;
@Override
public JSONObject selectNameByUserNo(String code) {
JSONObject object = new JSONObject();
String userNo = null;
String name = null;
try {
String token = weComUtil.getTokenWeCom(ymlConfig.getCorpId(),ymlConfig.getCorpsecret_kq());
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(ymlConfig.getUser_url()+"?access_token="+token+"&code="+code);
CloseableHttpResponse response = httpclient.execute(httpPost);
String resp;
try {
HttpEntity entity = response.getEntity();
resp = EntityUtils.toString(entity, "utf-8");
EntityUtils.consume(entity);
} finally {
response.close();
}
Gson gson = new Gson();
Map<String, Object> map = gson.fromJson(resp,
new TypeToken<Map<String, Object>>() {
}.getType());
userNo = map.get("UserId").toString();
log.info("通过企微获取到工号:"+userNo);
if(StrUtil.isNotBlank(userNo)){
String sql = "略";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, userNo);
name = maps.get(0).get("name").toString();
}
} catch (Exception e) {
log.info("通过工号查询名字后返回到考勤查看页面异常:"+e.getMessage());
e.printStackTrace();
}
object.put("userNo", userNo);
object.put("name", name);
return object;
}
/**
构造网页授权链接
redirectUrl:跳转的地址,即应用的访问地址(该地址必须是发布过的真实可用域名)
ymlConfig.getOauth_url():构造的授权链接
ymlConfig.getCorpId():企业id(在企业微信管理页面的企业信息中可以找到)
**/
@Override
public String weComGetUserNo() {
String requestUrl = null;
try {
String redirectUrl = URLEncoder.encode(ymlConfig.getRedirectUrl(),"UTF-8");//url地址需要进行处理
requestUrl = ymlConfig.getOauth_url().replace("CORPID", ymlConfig.getCorpId())
.replace("REDIRECT_URI", redirectUrl);
System.out.println(requestUrl);
}catch (Exception e){
log.info("企业微信获取构造网页授权链接接口异常:"+e.getMessage());
e.printStackTrace();
}
return requestUrl;
}
}
2、考勤代码
查询数据库对应人的考勤数据,并返回结果
Controller代码
import com.alibaba.fastjson.JSONObject;
import com.goldensea.kqhy.common.Result;
import com.goldensea.kqhy.entiy.Attendance;
import com.goldensea.kqhy.service.ApiService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
@Api(value = "前台调用后台接口", tags = {"前台调用后台接口"})
public class ApiController {
@Autowired
private ApiService apiService;
@ApiOperation("查询指定日期的考勤数据")
@PostMapping("/getAttendance")
public Result getAttendance(@RequestBody Attendance attendance){
Result<JSONObject> result = null;
try {
JSONObject json = apiService.selectKQ(attendance.getUserNo(),attendance.getDate());
return Result.success(json);
}catch (Exception e){
e.printStackTrace();
return Result.failure(000,e.getMessage());
}
}
@ApiOperation("查询考勤异常数据")
@PostMapping("/getAttendanceError")
public Result getAttendanceError(@RequestBody Attendance attendance){
Result<JSONObject> result = null;
try {
JSONObject json = apiService.selectKQError(attendance.getUserNo());
return Result.success(json);
}catch (Exception e){
e.printStackTrace();
return Result.failure(000,e.getMessage());
}
}
}
Service代码
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.goldensea.kqhy.service.ApiService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Service
public class ApiServiceImpl implements ApiService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public JSONObject selectKQ(String userNo, String date) {
JSONObject object = new JSONObject();
JSONArray array = new JSONArray();
//查询打卡记录
if(StrUtil.isNotBlank(userNo)||StrUtil.isNotBlank(date)){
String sql = "略";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql,userNo,date);
for (Map<String, Object> map : maps) {
String string = map.get("dksj").toString();
array.add(string.substring(11,16));
}
object.put("arry",array);
}
//查询班次信息
String sql1 = "略";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql1,userNo,date);
for (Map<String, Object> map : maps) {
String bc = map.get("bc").toString();//班次
String qq = map.get("qq").toString();//缺勤
object.put("shift",bc);
if(Integer.parseInt(qq)>0){
object.put("absenteeism",Integer.parseInt(qq));
}else {
object.put("absenteeism",null);
}
}
return object;
}
@Override
public JSONObject selectKQError(String userNo) {
JSONObject object = new JSONObject();
JSONArray array = new JSONArray();
//查询考勤异常信息
String sql = "略";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql,userNo);
for (Map<String, Object> map : maps) {
array.add(map.get("qqdate").toString());//考勤异常日期
}
object.put("arry",array);
return object;
}
}
三、企业微信集成
我们开发好一个页面,要怎么挂到企微上面去呢,首先得你得有企业微信管理员的权限,其次需要把你开发好的页面,注册一个和你企业挂钩的二级域名(例如企业注册的域名是:www.xxx.com,那么你注册的域名便要像:www.yyy.xxx.com),是先准备好上面两部就可以来进行配置了
登录企业微信后台(企业微信),选择 应用管理-》新建应用
这个自建页面的主页不是我们最终返回的一个主页,我这边是做了一个重定向处理,先通过这个路径来获取到当前用户的code,通过code去调用企业微信接口,获取工号,然后再返回出考勤页面,具体代码在第二部分的企微代码有说明实现
进行调用企业微信获取code时,我们需要授一下权,设置可信域名
自此我们集成企微就搭建好了,只是简单的获取企微的用户工号,回显考勤页面数据,其实还有更多好玩的操作,如配置JS-SDK我们可以获取更多的用户信息,这个我就没去研究了,哈哈哈
总结
通过这次任务,我也学会了如何去集成企微和前端开发的一些知识,其中一些代码和操作也是参考了这些大佬的文章
(企业微信集成应用程序、服务最详细教程_集成企业微信应用免登录csdn-CSDN博客,Element-UI使用el-calendar显示工作日-CSDN博客),感谢感谢!如果各位道友看到我的文章对您也有所帮助的话,也可以点赞收藏一下哟~,有什么问题我们可以在评论区探讨解决,好的,今天的工作记录就写到这了,再会~~~