随着工作的积累,我们平时做过的项目越来越多,我们每做一个新项目,都习惯新建一个文件夹,里面新建一个 readme.txt,里面记录下该项目的一些信息,比如测试、开发、生产环境服务器地址,各种账号、密码,各种原型、UI在线地址,需要对接的第三方硬件 API 文档地址等等,随着做过的项目越来越多,这种乱七八糟的东西越来越多,后续遇到以前项目的问题,也是进入到对应项目的文件夹中打开其readme.txt中一个一个找,或者使用 ctrl + f搜索,感觉都是很费劲的,于是我写了一个工具,这个工具很简单,就是一个 html页面,里面没引入任何第三方的依赖,完全是原生的 javascript、原生dom语法,所以直接双击浏览器打开即可使用,如下是源码:
searchTool.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>search tool</title>
<style type="text/css">
.hide{
display: none;
}
.colorBoard {
display: flex;
width:1000px;
}
.color {
float:left;
border: 1px solid white;
font-size:13px;
width:40px;
height:60px;
writing-mode: vertical-rl;
text-orientation: upright;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
padding-top:5px;
padding-right:5px;
cursor: pointer;
}
.selectedColor{
border: 2px solid black;
font-size:19px;
width:60px;
height:90px;
padding-top:8px;
padding-right:8px;
}
.container{
margin-top:0px;
margin-bottom:20px;
border:2px solid black;
padding-top:-50px;
}
.title{
border: 1px solid black;
margin-top:-20px;
margin-bottom:-20px;
padding-left:20px;
}
button{
cursor: pointer;
background-color: rgb(192, 192, 192);
}
select{
background-color: rgb(192, 192, 192);
}
input{
background-color: rgb(192, 192, 192);
}
body{
white-space: pre;
background-color:grey;
}
.similarity{
margin-top:-20px;
margin-bottom:-20px;
border: 1px solid black;
font-size:11px;
padding-left:20px;
}
.box{
margin-top:-20px;
margin-left:-100px;
margin-bottom:-50px;
padding-bottom:0px;
}
.btn_copy{
.font-size:6px;
}
.highlight{
color:yellow;
}
.highlight2{
background-color:yellow
}
.btn_copy {
position: relative;
display: inline-block;
cursor: pointer;
}
.btn_copy::after {
content: "点击即可复制文本";
visibility: hidden;
opacity: 0;
transition: opacity 0.3s;
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background-color: #000;
color: #fff;
padding: 5px;
border-radius: 5px;
}
.btn_copy:hover::after {
visibility: visible;
opacity: 1;
}
#containers{
margin-top:-40px;
}
</style>
</head>
<body>
<div class="colorBoard">
<div class="color selectedColor" style="background-color:#CCE9CC" color="#CCE9CC" onclick="setSelectedColor()">豆沙绿</div>
<div class="color" style="background-color:#428056" color="#428056" onclick="setSelectedColor()">薄荷绿</div>
<div class="color" style="background-color:#51a584" color="#51a584" onclick="setSelectedColor()">竹绿</div>
<div class="color" style="background-color:#4993ba" color="#4993ba" onclick="setSelectedColor()">钴蓝</div>
<div class="color" style="background-color:#096e37" color="#096e37" onclick="setSelectedColor()">鹦鹉绿</div>
<div class="color" style="background-color:#eb8aa7" color="#eb8aa7" onclick="setSelectedColor()">报春红</div>
<div class="color" style="background-color:#5ac4c4" color="#5ac4c4" onclick="setSelectedColor()">石绿</div>
<div class="color" style="background-color:#5a98c3" color="#5a98c3" onclick="setSelectedColor()">晴蓝</div>
<div class="color" style="background-color:#bbcdda" color="#bbcdda" onclick="setSelectedColor()">云水蓝</div>
<div class="color" style="background-color:#f4cf6b" color="#f4cf6b" onclick="setSelectedColor()">炒米黄</div>
<div class="color" style="background-color:#df7824" color="#df7824" onclick="setSelectedColor()">麂棕</div>
<div class="color" style="background-color:#f27835" color="#f27835" onclick="setSelectedColor()">蟹殼紅</div>
<div class="color" style="background-color:#7f2a21" color="#7f2a21" onclick="setSelectedColor()">胭脂红</div>
<div class="color" style="background-color:#6aa795" color="#6aa795" onclick="setSelectedColor()">梧枝绿</div>
<div class="color" style="background-color:#432533" color="#432533" onclick="setSelectedColor()">玫瑰紫</div>
<div class="color" style="background-color:#65bcd0" color="#65bcd0" onclick="setSelectedColor()">齊青</div>
<div class="color" style="background-color:#2d3250" color="#2d3250" onclick="setSelectedColor()">靛蓝</div>
<div class="color" style="background-color:#d3c13e" color="#d3c13e" onclick="setSelectedColor()">珐琅黄</div>
<div class="color" style="background-color:#4f030d" color="#4f030d" onclick="setSelectedColor()">波尔多红</div>
<div class="color" style="background-color:#033355" color="#033355" onclick="setSelectedColor()">普鲁士蓝</div>
<div class="color" style="background-color:#81031f" color="#81031f" onclick="setSelectedColor()">勃艮第红</div>
<div class="color" style="background-color:#0396b6" color="#0396b6" onclick="setSelectedColor()">邦迪蓝</div>
<div class="color" style="background-color:#8f4d29" color="#8f4d29" onclick="setSelectedColor()">木乃伊棕</div>
<div class="color" style="background-color:#1d589b" color="#1d589b" onclick="setSelectedColor()">卡布里蓝</div>
<div class="color" style="background-color:#b15c24" color="#b15c24" onclick="setSelectedColor()">提香红</div>
<div class="color" style="background-color:#81d8d0" color="#81d8d0" onclick="setSelectedColor()">蒂芙尼蓝</div>
<div class="color" style="background-color:#9f3025" color="#9f3025" onclick="setSelectedColor()">覆盆子红</div>
<div class="color" style="background-color:#e2b141" color="#e2b141" onclick="setSelectedColor()">虎皮黄</div>
<div class="color" style="background-color:#438bb5" color="#438bb5" onclick="setSelectedColor()">鸢尾蓝</div>
<div class="color" style="background-color:#9bb6d1" color="#9bb6d1" onclick="setSelectedColor()">星蓝</div>
<div class="color" style="background-color:#cfb74a" color="#cfb74a" onclick="setSelectedColor()">草黄</div>
<div class="color" style="background-color:#dae5e4" color="#dae5e4" onclick="setSelectedColor()">云峰白</div>
<div class="color" style="background-color:#c3c5c5" color="#c3c5c5" onclick="setSelectedColor()">月影白</div>
<div class="color" style="background-color:#8f2d62" color="#8f2d62" onclick="setSelectedColor()">苋菜紫</div>
<div class="color" style="background-color:#121723" color="#121723" onclick="setSelectedColor()">钢蓝</div>
<div class="color" style="background-color:#8e532c" color="#8e532c" onclick="setSelectedColor()">岩石棕</div>
<div class="color" style="background-color:#b9cf8d" color="#b9cf8d" onclick="setSelectedColor()">橄榄石绿</div>
</div>
<a name="top"></a>
最低相似度:<input id="input_similarityThreshold" value="0.0" /><br/>
<input id="input_search" type="text"/><button onclick="doSearch()" style="cursor: pointer;">搜索</button><br/><br/>
<button onclick="collectAllProjectTitles2SelectOptions()" style="cursor: pointer;">刷新下拉列表</button> <button onclick="openOptions()" style="cursor: pointer;">展开下拉列表</button> <button onclick="closeOptions()" style="cursor: pointer;">折叠下拉列表</button>
<input id="input_search_options" type="text" onfocus="openOptions()" onkeyup="highlightMatchedOptionBySearchInput()"/>
<select id="select_projectTitles" onclick="printSelectedProjectTitle()" ></select> <button onclick="doSearch4select()" style="cursor: pointer;">搜索</button>
<div id="containers">
<div class="container">
<div class="title">项目001:</div>
<div class="similarity"></div>
<div class="box" id="box001">
<a href="xxx" target="_blank">xxx</a>
<button class="btn_copy">复制</button>
我是项目001的内容信息( 支持标签,比如<a>超链接</a>等 )
我是项目001的内容信息( 支持标签,比如<a>超链接</a>等 )
我是项目001的内容信息( 支持标签,比如<a>超链接</a>等 )
我是项目001的内容信息( 支持标签,比如<a>超链接</a>等 )
我是项目001的内容信息( 支持标签,比如<a>超链接</a>等 )
我是项目001的内容信息( 支持标签,比如<a>超链接</a>等 )
</div>
</div>
<div class="container">
<div class="title">项目002:</div>
<div class="similarity"></div>
<div class="box" id="box002">
<a href="xxx" target="_blank">xxx</a>
<button class="btn_copy">复制</button>
我是项目002的内容信息( 支持标签,比如<a>超链接</a>等 )
我是项目002的内容信息( 支持标签,比如<a>超链接</a>等 )
我是项目002的内容信息( 支持标签,比如<a>超链接</a>等 )
我是项目002的内容信息( 支持标签,比如<a>超链接</a>等 )
我是项目002的内容信息( 支持标签,比如<a>超链接</a>等 )
我是项目002的内容信息( 支持标签,比如<a>超链接</a>等 )
</div>
</div>
</div>
<a href="#top">回到顶部</a>
</body>
<script type="text/javascript">
var div_containers = document.getElementById("containers");
var input_search = document.getElementById("input_search");
var input_similarityThreshold = document.getElementById("input_similarityThreshold");
var similarity_threshold = 0;
var selectedColor = "#CCE9CC";
var innerSearchText = null;
var map_boxId_originInnerHTML = {};
var title_select = null;
function setSelectedColor() {
var colors = document.getElementsByClassName("color");
for (var i = 0; i < colors.length; i++) {
colors[i].classList.remove("selectedColor");
}
var selectedDiv = event.target;
selectedDiv.classList.add("selectedColor");
selectedColor = selectedDiv.getAttribute("color");
doSearch();
}
function doSearch(){
var title_search = input_search.value;
doSearch_inner( title_search );
}
function doSearch4select(){
if( !title_select ){
return;
}
doSearch_inner( title_select );
}
function doSearch_inner( title_search ){
var similarity_threshold = input_similarityThreshold.value;
var containerObj_array = [];
// 遍历 div_containers 下面的 全部 div_container,然后获取每个 div_container 的 title
const containers = div_containers.querySelectorAll('.container');
// console.log( "title_search = " + title_search );
containers.forEach(container => {
// 对每个匹配的div元素进行操作
var title = container.querySelector( ".title" ).innerHTML;
// console.log( "title = " + title );
var div_similarity = container.querySelector( ".similarity" )
// 可以使用相似度算法,相似度超过 60%即输出
const similarity = calculateSimilarity( title_search, title,true );
console.log( title + "( " + similarity + " )" );
div_similarity.innerHTML = "相似度:" + similarity;
// 将此 container 保存到临时集合中
var containerObj = {
"container": container,
"similarity": similarity
};
containerObj_array.push( containerObj );
});
console.log( "containerObj_array.length = " + containerObj_array.length );
// 对 containerObj_array 排序( 相似度越高越排在上面 )
containerObj_array.sort( function( a,b ){
return b.similarity - a.similarity;
} );
// 清空 div_containers
div_containers.innerHTML = "";
for( var i in containerObj_array ){
var containerObj = containerObj_array[ i ];
var container = containerObj.container;
var similarity = containerObj.similarity;
if( i == 0 ){
// 相似度最高的,添加背景色高亮命中提示
container.style.backgroundColor = selectedColor;
}else{
container.style.backgroundColor = "#b3b3b2";
}
// 如果相似度大于等于相似度阈值,则显示,否则隐藏
if( similarity >= similarity_threshold ){
// 显示
container.removeAttribute("hidden");
}else{
// 移除
container.setAttribute("hidden", "hidden");
}
div_containers.appendChild( container );
}
}
// 字符串相似度算法
function calculateSimilarity(str1, str2,ignoreCase) {
if( ignoreCase ){
str1 = str1.toLowerCase();
str2 = str2.toLowerCase();
}
var m = str1.length;
const n = str2.length;
// 创建二维数组并初始化
const dp = [];
for (let i = 0; i <= m; i++) {
dp[i] = [];
dp[i][0] = i;
}
for (let j = 0; j <= n; j++) {
dp[0][j] = j;
}
// 动态规划计算编辑距离
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (str1[i - 1] === str2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(
dp[i - 1][j] + 1, // 删除操作
dp[i][j - 1] + 1, // 插入操作
dp[i - 1][j - 1] + 1 // 替换操作
);
}
}
}
// 返回相似度
return 1 - dp[m][n] / Math.max(m, n);
}
function copyToClipboard(content) {
var textarea = document.createElement('textarea');
textarea.value = content;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
function highlightText( box ) {
var boxId = box.id;
var originInnerHTML = map_boxId_originInnerHTML[ boxId ];
if( !originInnerHTML ){
originInnerHTML = box.innerHTML;
map_boxId_originInnerHTML[ boxId ] = originInnerHTML;
}
if( !originInnerHTML.includes( innerSearchText ) ){
// 未匹配到,直接返回
alert( "未匹配到,直接返回" );
return;
}
var innerHTML_highlight = originInnerHTML.replace(new RegExp(innerSearchText, "gi"), "<span class='highlight'>$&</span>");
box.innerHTML = innerHTML_highlight;
}
function recordInnerSearchText(input){
innerSearchText = input.value;
// console.log( innerSearchText );
}
function doBoxInnerSearch(button){
if( !innerSearchText ){
alert("无需搜索...");
}
highlightText( button.parentNode );
}
function collectAllProjectTitles2SelectOptions(){
var selectElement = document.getElementById("select_projectTitles");
selectElement.innerHTML = "";
var containers = document.getElementsByClassName("container");
for (var i = 0; i < containers.length; i++) {
var projectTitle = containers[i].getElementsByClassName("title")[0].innerHTML;
// console.log( projectTitle );
var optionElement = document.createElement("option");
optionElement.innerHTML = projectTitle;
selectElement.appendChild(optionElement);
}
clearSelectStatus();
}
function refreshBoxContent2OriginInnerHTML(button) {
var container = button.parentNode; // 获取父元素
var box = container.querySelector('.box'); // 获取子元素
var boxId = box.id;
var originInnerHTML = map_boxId_originInnerHTML[ boxId ];
if( originInnerHTML ){
box.innerHTML = originInnerHTML; // 修改子元素的innerHtml
}
}
function printSelectedProjectTitle() {
// clearSelectStatus();
var selectElement = document.getElementById("select_projectTitles");
var selectedIndex = selectElement.selectedIndex;
var selectedOption = selectElement.options[ selectedIndex ];
if( !selectedOption ){
return;
}
var selectedProjectTitle = selectedOption.innerHTML;
console.log( selectedProjectTitle );
title_select = selectedProjectTitle;
}
function openOptions() {
var selectElement = document.getElementById("select_projectTitles");
// selectElement.size = selectElement.options.length;
selectElement.size = 15;
}
function closeOptions() {
var selectElement = document.getElementById("select_projectTitles");
selectElement.size = 1;
}
function highlightMatchedOptionBySearchInput() {
var inputText = document.getElementById("input_search_options").value;
if( !inputText ){
return;
}
// clearSelectStatus();
var selectElement = document.getElementById("select_projectTitles");
var count=0;
console.log("--------------------");
for (var i = 0; i < selectElement.options.length; i++) {
var option = selectElement.options[i];
if (option.innerHTML.includes(inputText)) {
// option.selected = true;
// title_select = option.innerHTML;
// console.log( title_select );
// count++;
// break;
option.classList.add("highlight2");
}else{
option.classList.remove("highlight2");
}
}
// if( title_select && count > 1 ){
// title_select = null;
// }
}
function clearSelectStatus(){
// 获取select元素
// var selectElement = document.getElementById("select_projectTitles");
// 清空选中状态
// var options = selectElement.options;
// for (var i = 0; i < options.length; i++) {
// options[i].selected = false;
// }
// 清空选中项
// selectElement.selectedIndex = -1;
}
// 为所有的 box div 元素添加几个基本的操作 元素
function addSomeBaseElementForAllBoxDiv(){
// 获取所有 class="box" 的 div 元素
var boxes = document.querySelectorAll(".box");
for (var i = 0; i < boxes.length; i++) {
var box = boxes[i];
var input = document.createElement("input");
input.setAttribute("type", "text");
input.setAttribute("onkeyup", "recordInnerSearchText(this)");
input.setAttribute( "style","margin-left:150px;" );
var button = document.createElement("button");
button.setAttribute("onclick", "doBoxInnerSearch(this)");
button.innerHTML = "搜索";
box.insertBefore(button, box.firstChild);
box.insertBefore(input, box.firstChild);
}
}
// 为所有的 class="container" 的 div 添加一些操作按钮
function addSomeOptButtonsForAllContainer(){
// 获取所有 class="container" 的 div 元素
var containers = document.querySelectorAll(".container");
for (var i = 0; i < containers.length; i++) {
var container = containers[i];
var button_refresh = document.createElement("button");
button_refresh.setAttribute("style", "margin-left:50px;");
button_refresh.setAttribute("onclick", "refreshBoxContent2OriginInnerHTML(this)");
button_refresh.innerHTML = "刷新";
var button_hideOrOpen = document.createElement("button");
button_hideOrOpen.setAttribute("style", "margin-left:50px;");
button_hideOrOpen.setAttribute("onclick", "hideOrOpenBox(this)");
button_hideOrOpen.innerHTML = "折叠︽";
var a_back2Top = document.createElement("a");
a_back2Top.setAttribute("href", "#top");
a_back2Top.setAttribute("style", "margin-left:50px;");
a_back2Top.innerHTML = "↑回到顶部";
container.insertBefore(a_back2Top, container.firstChild);
container.insertBefore(button_hideOrOpen, container.firstChild);
container.insertBefore(button_refresh, container.firstChild);
}
}
function hideOrOpenBox(button){
var container = button.parentNode;
var box = container.querySelector(".box");
if( button.innerHTML=='折叠︽' ){
// 折叠操作
box.setAttribute( "style","display:none;" );
button.innerHTML = "展开︾";
}else{
// 展开操作
box.removeAttribute( "style" );
button.innerHTML = "折叠︽";
}
}
window.onload = function(){
// todo 为所有的 box div 元素添加几个基本的操作 元素
addSomeBaseElementForAllBoxDiv();
collectAllProjectTitles2SelectOptions();
// 获取所有class为"xxx"的button元素
var buttons = document.getElementsByClassName("btn_copy");
// 遍历所有button元素,并为每个元素添加点击事件
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", function() {
// 输出当前点击的button元素的innerHTML
var content = this.innerHTML;
// console.log( content );
// content = content.replace("&", "&");
// alert(content);
content = content.replace(/\&/g, "&");
content = content.replace(/\>/g, ">");
content = content.replace(/\</g, "<");
copyToClipboard( content );
});
}
// 为所有的 class="container" 的 div 添加一些操作按钮
addSomeOptButtonsForAllContainer();
}
</script>
</html>
使用效果:
这里 “命中” 的依据并不是简单的 “equals”、"contains"或者 高级一点的 "正则",因为它们都需要我们对之前做过的项目名记得很清楚,我这里使用的是计算2个字符串的相似度( 编辑距离 ),这样只需要大概记住之前的项目名就可以很好的搜出排名靠前的相似度而被命中,比如做过一个系统,当时录入进去的名称是 “安徽省国家电网系统”,则如果是常规的搜索,必须查询 "安徽省国家电网系统"、"国家电网"、"安徽省国家电网"才能搜到,可是随时时间的推移,我们大部分人脑袋中的印象都是 “安徽国电系统”、“安徽电网”这种零碎的名称,所以此处使用字符串编辑路径相似度算法就可以很好的解决这个问题