本系列是本人在OPPO工作一年半中,为了解决开发或者运营的困扰,自制的一些小工具合集,特地以文章的形式记录下来,一方面作为自己的知识储备,另一方面期望能够给遇到相同问题的同学一些启发。
运营苦恼
埋点数据平台中有上百条字段,但运营只想选取其中的十几条字段,然后做成 excel 报表。可令人头痛的是,这个系统又是其他团队用 PHP 开发的,埋点数据系统的后台不支持 过滤出指定字段,并且取数据的时候需要手动复制到本地的 Excel 表格,整理一个活动的数据有时候会消耗运营半天时间。
从问题中我们分析到:查找指定数据 和 制作Excel表格 是运营耗时的重要原因,如何提升呢?
- 过滤出指定数据表格,提高查找效率。
- 支持以方便的形式将表格导出为 Excel,减少人工的复制粘贴操作。
知道了要做的方向,接下来就应该设计如何解决问题了。
编写脚本
我们的第一版方案很简单,写一段JS脚本,通过操纵dom的方式,模拟人工选择点击按钮,选中输入的指定字段。
let targetStr = 'angular vue react';
let ul = dropdownMenu;
let children = ul.children;
let targetArr = targetStr.split(" "); // 生成目标数组
for (let i = 0; i < children.length; i++) {
let li = children[i].children[0];
let label = li.innerHTML;
let input = li.children[0];
for (let j = 0; j < targetArr.length; j++) {
if (label.indexOf(targetArr[j]) > -1) {
input.click();
}
}
}
这里只演示大致含义:生成需要选中的字段数组,遍历所有所有的单选框,遍历目标字段数组匹配完成之后,触发click事件,完成选中所有按钮的效果,下面是生成excel表格的逻辑。
function tableToExcel(fileName, excelContent) {
var uri = "data:application/vnd.ms-excel;base64,";
var fileName = fileName || "excelexport";
var template =
'<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta name="renderer" content="webkit"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>{table}</table></body></html>';
var ctx = { worksheet: "Worksheet", table: excelContent };
var a = document.createElement("a");
a.hreflang = "zh";
a.type = "application/vnd.ms-excel";
let blob = new Blob([format(template, ctx)]);
a.href = URL.createObjectURL(blob);
a.target = "_blank";
a.download = fileName + ".xls";
a.tableBorder = 1;
document.body.appendChild(a);
a.click();
// 点击下载完成后,删除创建的a标签
setTimeout(function () {
document.body.removeChild(a);
}, 200);
}
document.getElementById('targetTable').addEventListener("dblclick", function (e) {
tableToExcel("表格", e.target.innerHTML);
});
这里的逻辑大致是:给指定的表格,绑定一个双击事件,双击下载 Excel 表格。其中不难发现,其实触发下载的逻辑很简单:
- 拼接一段特定的字符串,生成一个
Blod
对象 - 创建一个
a
标签,href
参数为一个Blod
对象生成的 url 。
最后,我们把这两段代码粘贴到谷歌的 console 控制台中,回车就可以运行了,这样第一个版本就完成了。
咱们思维发散一下,能否实现一个函数,输入特定格式的数组,然后生成一个excel表格呢?其实无非就是重新组装一个 table
标签罢了。
优化体验
其实不难发现,作为一个小工具,给技术人员使用很方便,但对于运营等非技术人员,会产生一些问题。
- 流程繁琐。需要打开控制台,然后然后跑代码。
- 需要手动修改代码。过滤字符串的流程需要修改代码里面的
targetStr
字符串。 - 重复操作。每次过滤,都需要跑一遍代码。
- 学习成本较高。每个运营都要学习一遍在控制台内运行代码的方法
其实针对上述的操作流程,很简单,我们用脚本在页面内插入一个 input
输入框。通过这样的方式,运营只需要跑一次代码就行了。
但是,有没有方法避免运营同学跑代码,界面化操作呢?很简单,编写一个谷歌插件!
谷歌插件
谷歌插件可以给当前的网页插入一段JS脚本,同时也可以在顶部增加一个弹窗,这个弹窗可以用一个新的 html 实现。
- 通过弹窗实现一个 input 让运营输入需要选中的字段。
- 在网页内插入一段脚本,给所有的表格添加上双击下载事件,并且接收弹窗输入值完成过滤逻辑。
因为涉及到部分业务代码,在此只提供部分逻辑,仅供大家参考,改编自项目 chrome-plugin-demo,关于怎样编写谷歌教学很全面。
// manifest.json
{
"manifest_version": 2,
"name": "埋点数据平台插件",
"version": "1.0",
"description": "此插件仅用于埋点数据平台",
"author": "ting",
"icons": {
"48": "icon.png",
"128": "icon.png"
},
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html" // 指定弹窗页面名称。
},
"content_scripts": [
{
"matches": ["*://baidu.com/*"], // 限制只能在百度域名下运行此插件
"js": ["content-script.js"] // 设定插入
}
]
"permissions": ["tabs", "storage"], // 申请权限:tabs和storage(用于弹窗和注入JS沟通)
}
上面主要是配置我们的弹窗和注入JS的文件,这个文件会告诉谷歌浏览器解析逻辑的路径。
<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>popup</title>
<style>
p {
margin: 0;
padding: 10px 0;
}
</style>
</head>
<body style="width:500px;min-height:100px;">
<textarea name="" id="textarea" cols="60" rows="10"></textarea>
<p>*需要过滤的字段需要用<b>空格</b>分开,示例:pv-login pv-channel</p>
<button id="filter-button">过滤</button>
<script type="text/javascript" src="popup.js"></script>
</body>
</html>
弹窗主要有三个元素:一个taxtarea 输入框,一个确认按钮,还有一段提示文案。
// popup.js
// 将textarea中的文案传递给 content-script.js(注入窗口的JS文件)
function sendMessage(value) {
chrome.tabs.query(
{
active: true,
currentWindow: true,
},
(tabs) => {
let message = {
data: value,
};
chrome.tabs.sendMessage(tabs[0].id, message);
}
);
}
// 关闭弹窗时会销毁数据,所有使用 storage 缓存当前输入的内容。
chrome.storage.local.get(["ltExportExcelVal"], function (result) {
document.getElementById("textarea").value = result.ltExportExcelVal || "";
});
// 点击过滤按钮
document.getElementById("filter-button").addEventListener("click", function () {
let value = document.getElementById("textarea").value;
sendMessage(value);
// 缓存上一次确认输入的内容
chrome.storage.local.set({ ltExportExcelVal: value }, function () {
console.log("Value is set to " + value);
});
});
在 popup.js 中主要做了两件事:将输入信息传递出去和缓存上次输入的内容。
// 因为 content-script.js 插入后,有可能table表格还没有完成加载。
// 所以需要等页面加载完毕,然后给每个表格添加上一个事件。
(function (win) {
"use strict";
var listeners = [];
var doc = win.document;
var MutationObserver = win.MutationObserver || win.WebKitMutationObserver;
var observer;
function check() {
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
var elements = doc.querySelectorAll(listener.selector);
for (var j = 0; j < elements.length; j++) {
var element = elements[j];
if (!element.ready) {
element.ready = true;
listener.fn.call(element, element);
}
}
}
}
function ready(selector, fn) {
listeners.push({
selector: selector,
fn: fn,
});
if (!observer) {
observer = new MutationObserver(check);
observer.observe(doc.documentElement, {
childList: true,
subtree: true,
});
}
check();
}
win.ready = ready;
})(this);
// 给所有table表格添加双击事件
function tableToExcel(fileName, excelContent) { //... }
// 给所有 table 添加一个双击事件。
ready("table", function (element) {
element.addEventListener("dblclick", function () {
tableToExcel("表格", element.innerHTML);
});
document.getElementById('targetTable').addEventListener
});
// 获取弹窗中传递过来的值,过滤出指定字段
chrome.runtime.onMessage.addListener((request) => {
let targetStr = request.data;
// ...模拟用户点击过滤出指定table字段。
});
通过这个谷歌插件,我获得了一大堆运营给我的零食。
一些思考和总结
其实这个小工具的开发原理和逻辑都和简单,但它能够帮助运营或者帮助其他同学减轻很多无效的工作量。其实作为前端开发,我们的技能不应该仅仅在用在业务上面,如果能帮助到周围的同事和家人解决问题,也能够实现自身的价值。
另外,第一次了解到原来 html
可以直接导出 Excel 文件,xlsx 文件原理竟然是 xml 格式文件,还可以直接解压,同理 html
也可以导出 html, txt 等格式的文件,利用这个原理我们可以实现更多有趣且实用的功能!