背景
开发这个工具是因为一句抱怨
故事是这样的,我们公司是一个非常重视员工健康的公司,一年前老董说让HR(后面改为ZT)督促员工多多运动,可持续地位公司创造价值.并拿出了一部预算来奖励那些积极运动的人.于是我们公司一百多人,被ZT分为三组,一组大约37人,然后每月统一下每组的运动积分, 男生8公里3分,女生6公里三分, 未来督促大家积极参与集体运动,HR有说, 每组的总积分为: 总分数 * 总参与人数 / 总人数
具体的统计方式是这样的,每个人跑了步后,只需将运动截图通过公司办公软件发给ZT,然后ZT会在月末的时候,将全公司一百多人的聊天记录扒一边,找到发送给她的运动截图.统计到excel里,这里还要查这个人是那一组的,应该给多少积分.过程极其繁杂,并且容易出错.需要收集的信息也很多.因为算运动积分这事只有她自己,经常导致我们到了下个月十号左右才出来运动结果,因为如果遗漏了谁的记录,需要ZT再去补充,计算.反正很麻烦. 那么麻烦,妹子当然有怨言啊,这是一件吃力不讨好的事情,算不准确,还会被人质疑能力有问题.反正我就被她漏掉过一次运动积分,还有一次日期记错了. 妹子在运动群里的抱怨,
大家都看到了,作为开发人员的大家,都没当一回事,有的还调侃了一下. 这个时候我略作思考就提出了很合理,高效的方案.
这个方案将收集信息的操作,下发到个人,而管理员只担任一个审核积分,跑步截图和计算积分的角色.并最终输出报表.
说完这句话我就后悔了, 因为我想到了一个更加优秀的方案来解放生产力, 在这里我也想问一下,屏幕前的你,如果把这个计算运动积分的事情交给你,你要怎么做? 好好想想.....可以先别往下看
首先我们分析一下, 在Confluence 我们可以创建一个运动模板,添加一个表格, 可以设置几列,这个表格可以无限地往下加,每个人运动了,都可以在上面添加一条记录 这个表格就长这个样子
标题,内容就是一个表格
想到表格我们就知道了,表格属于一种结构化的数据, 所谓结构化数据 就是 结构化数据也称作行数据,是由二维表结构来逻辑表达和实现的数据,严格地遵循数据格式与长度规范,主要通过关系型数据库进行存储和管理(来自百度百科)。
大致意思就是,表头明确定义了这一列是什么数据,第一列是名字,你就不能把运动积分填到第一列. 结构化的数据安装一定的规则排列,填写. 而处理结构化数据正是软件程序的拿手好菜....于是用软件来处理这些运动记录准没错. 所以我一看到这列表我就立即想到了可以使用TM写一个工具脚本来分组,计算积分,制作报表. Nice 说干就干....这对我来说轻车熟路. 因为我就是为了提高社会生产力而来,去吧 皮卡丘.....
技术方案
方向有了,就是制定思路
- 使用脚本拿到结构化数据
- 处理数据,积分相加,人员去重,分组
- 创建展示页面
- 使用html2canvas一键下载报表图片
思路有了,那就给它起一个名字吧, RM怎么样? report mark, report mother 报表制作, 报表之母 简洁明了.高大上.强, 我起名字的能力真是越来越强了...哈哈哈.... 中文释义为:基于页面结构化数据一键生成报表
经过下班后的几个晚上的艰苦调试,最后完成了这个不到300行代码的小产品
以下是产品使用的动态图
全部代码使用原生js写的, 样式使用了bootstrap,对原有页面有稍微一些影响.但不妨碍浏览信息. 下载报表使用的是html2canvas
高级功能 个人榜单 图表显示 需要使用星巴克咖啡充值 解锁
做这个功能的时候,感觉界面的味道不够,于是找了一张背包行者的图片, 瞬间界面提升了几个逼格...哈哈哈...于是我找到了制作一个优秀产品的小技巧, 善用背景 背景音乐, 背景图片, 都是很好的技巧.
关键代码如下:
欢迎点在,留言讨论. 关注我的TM专栏 有更多硬核干货
// ==UserScript==
// @name RM
// @namespace https://fizzz.blog.csdn.net/
// @version 0.1
// @description Report Maker: 基于页面结构化数据生成报表
// @author Fizz
// @match https://**/**
// @grant GM_addStyle
// @grant GM_getResourceText
// @require https://cdn.staticfile.org/html2canvas/0.5.0-beta4/html2canvas.min.js
// @resource bootstrapcss https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css
// ==/UserScript==
(function() {
'use strict';
let pageTitle = ''
let groupTotalPerson = {'第一组':37,'第二组':37,'第三组': 39} // 各组总人数
// 获取页面表格数据
function getPageTableData () {
let pageTableData = []
let allTr = document.querySelectorAll('#main-content .confluenceTable tbody [role="row"]'); // 获取所有行
[...allTr].forEach(trItem => {
let allTrInnerTd = trItem.querySelectorAll('td')
let name = allTrInnerTd[0].innerHTML.trim()
if (name !== '<br>' && name) {
let groupName = allTrInnerTd[1].innerText
let date = allTrInnerTd[2].innerText
let distance = allTrInnerTd[3].innerText
let integral = allTrInnerTd[4].innerText
let img = allTrInnerTd[5].innerText
let remark = allTrInnerTd[6].innerText
let trDataItem = {name,date,distance,integral,img,remark, groupName}
pageTableData.push(trDataItem)
}
})
return pageTableData
}
// 添加样式
function addStyle () {
let bootstrapcss = GM_getResourceText('bootstrapcss')
bootstrapcss = bootstrapcss + `
.fizz_inject_wrap_div{position: fixed;font-size: 14px !important;width: 100%;display: flex;justify-content: center;height: 100%;z-index: 100;z-index: 999;top:0;background: #fff;padding-top:20px;background-image:url('https://cf.qizhidao.com/download/attachments/63810224/1hikedr.png?version=1&modificationDate=1576220765550&api=v2')}
.text-danger.import-info{color: #CC9900;font-weight: 700;font-size:16px}
.myinject{position: fixed;bottom: 20px;right: 20px;vertical-align: middle;text-align: center;cursor: pointer;z-index: 999;padding: 5px 14px;border-radius: 4px;background-color: #2e6da4;border-color: #2e6da4;opacity: .2;color: #fff;}
.myinject:hover{opacity: 1;color: #fff;}
.fizz_operation_div{position: fixed;right: 30px;bottom: 50px;}
.fizz_operation_div * {margin: 0;padding: 0;border: 0;outline: 0;font-size: 14px;box-sizing: content-box;}
.fizz_operation_div ul,.fizz_operation_div li{list-style: none;border-left: 1px solid #2e6da4;border-right: 1px solid #2e6da4;margin: 0;padding: 0;outline: 0;font-size: 14px;box-sizing: content-box;}
.fizz_operation_div li{padding: 6px 0px;text-align: center;cursor: pointer;vertical-align: middle;line-height: 20px;height: 20px;}
.fizz_operation_div li:hover{background-color: #337ab7;color: #fff;}
.fizz_operation_div .fizz_main_btn {border: none;outline: none;padding: 8px 14px;border-radius: 4px;background-color: #2e6da4;border-color: #2e6da4;opacity: .5;color: #fff;vertical-align: middle;text-align: center;cursor: pointer;line-height: 16px;vertical-align: middle;}
.fizz_operation_div .fizz_main_btn:hover{opacity: 1;}
.fizz_operation_div .fizz_menu_ul{display: none;}
.fizz_operation_div:hover .fizz_menu_ul{display: block;}
.hidden{display:none}
`
GM_addStyle(bootstrapcss)
}
// 处理表格数据,返回Map
function showRes (pageTableData) {
const resMap = new Map();
// ...
let groupHtmlStringObj = ceateResTableDiv(resMap)
dataRender(groupHtmlStringObj)
}
// 创建显示结果的Div resMap:{'A': {},'B': {}}
function ceateResTableDiv (resMap) {
let groupHtmlStringObj = {
'第一组': {groupTotalIntegral: 0, groupTotalDistance: 0, groupTotalJoinNum: 0, finalTotalIntegral: 0, joinPersonData: []},
'第二组': {groupTotalIntegral: 0, groupTotalDistance: 0, groupTotalJoinNum: 0, finalTotalIntegral: 0, joinPersonData: []},
'第三组': {groupTotalIntegral: 0, groupTotalDistance: 0, groupTotalJoinNum: 0, finalTotalIntegral: 0, joinPersonData: []}
}
// ...
return groupHtmlStringObj
}
// 表格数据渲染到页面 {'第一组':{groupTotalIntegral: 0, groupTotalDistance: 0, groupTotalJoinNum: 0, joinPersonData: []},'第二组': [], '第三组': []}
function dataRender (groupObjData) {
console.log(groupObjData)
for(let [key,value] of Object.entries(groupObjData)) {
// ...
}
}
// 关闭报表页面,视图
function closeReportView () {
document.querySelector('.fizz_inject_wrap_div').classList.add('hidden')
}
// 导出报表为图片
function exportReportImg () {
// ...
}
// 创建报表页面
function creatReportWrapDiv () {
let reportWrapDiv = document.createElement('div')
let htmlString = `
<div>
....
</div>
`
reportWrapDiv.innerHTML = htmlString
reportWrapDiv.classList.add('fizz_inject_wrap_div')
reportWrapDiv.classList.add('hidden')
return reportWrapDiv
}
// 显示报表图标视图
function showReportChatView () {
alert('此功能仅限高级会员使用,请使用星巴克咖啡充值解锁')
}
// 显示个人维度图标视图
function showPersonView () {
alert('此功能仅限高级会员使用,请使用星巴克咖啡充值解锁')
}
// 页面初始化函数
function initFun () {
pageTitle = document.querySelector('#title-text').innerText
let body = document.querySelector('body')
let injectDiv = document.createElement('div')
injectDiv.classList.add('myinject')
injectDiv.title = '生成报表'
injectDiv.innerHTML = `R M`
injectDiv.onclick = function (e) {
document.querySelector('.fizz_inject_wrap_div').classList.remove('hidden')
let pageTableData = getPageTableData()
showRes(pageTableData)
}
addStyle()
body.appendChild(injectDiv)
let reportWrapDiv = creatReportWrapDiv()
body.appendChild(reportWrapDiv)
document.querySelector('#fizz_rm_close_report').addEventListener('click', closeReportView, false)
document.querySelector('#fizz_rm_export_report_img').addEventListener('click', exportReportImg, false)
document.querySelector('#fizz_rm_report_chart_img').addEventListener('click', showReportChatView, false)
document.querySelector('#fizz_rm_person_rank_img').addEventListener('click', showPersonView, false)
}
initFun()
})();
最后:
希望更多的人能够使用软件提高自己的工作效率,多点时间陪家人.多点时间去做自己喜欢做的事情.