大家好,我是诚一
几天前,一家使用我的进销存系统10年的客户找到我,想让我给他们系统增加类似于Excel表格的配方表管理子功能。
老客户有需求那还有什么好说的,立马答应下来并着手准备开工做。
一顿资料搜索后发现handsontable控件可以实现这样的需求,于是进一步阅读官网资料(全英文)并上网查找handsontable控件的相关文章。
废话少说,进入正题:
一、上github下载控件文件包
https://github.com/handsontable/handsontable
二、复制所需要的相关文件
1、解压后打开 dist 文件夹将文件handsontable.full.min.css复制到自己项目的css目录中,将handsontable.full.min.js文件复制到项目的js目录中。
2、将 languages文件夹中 zh-CN.js(如果希望菜单使用其他语言则复制对应语言文件,本例为简体中文)文件复制到js目录中。
3、将 test\lib 文件夹中 jquery.min.js 文件
在项目的js目录新建common.js和excel.js文件,此时js目录内容如下
4、在css目录中新建 handsontable.formula.css 文件,用于设置 formula 效果的样式
三、开始写代码(本项目后端使用的是asp开发语言用于读默认excel表格和保存表格,前端使用html,数据库为access,也可很方便自行改为其他类型数据库)
1、新建index.html文件,文件内容如下
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Handsontable简易样例程序</title>
<link rel="stylesheet" href="css/handsontable.full.min.css" />
<link rel="stylesheet" href="css/handsontable.formula.css" />
<script language="javascript" type="text/javascript" src="js/jquery.min.js"></script>
<script src="js/handsontable.full.min.js" type="text/javascript" language="javascript"></script>
<script src="js/zh-CN.js" type="text/javascript" language="javascript"></script>
<script src="js/common.js" type="text/javascript" language="javascript"></script>
<script src="js/excel.js" type="text/javascript" language="javascript"></script>
</head>
<body>
<div id="exceltable"></div>
<br />
<input type="button" class="button" id="btnSave" value="保存" />
</body>
</html>
我建的index.html文件为utf-8编码,如果不是些编码请将第 10 行改为以下内容(增加 charset="utf-8"),否则右键菜单将出现乱码。
<script src="js/zh-CN.js" type="text/javascript" language="javascript" charset="utf-8"></script>
2、修改css/handsontable.formula.css文件,内容如下
formula为公式正常记算后的单元格样式,formula-error为公式计算出错或单元格数据类型错误时样式。
td.formula {
background-color:#d7d765;
}
td.formula-error {
background-color:#d65e5e;
color:white;
}
3、编写js/excel.js文件内容如下
var hot,formulaDatas;
var queryFirst,queryPrice,querySoft,queryHard,queryCost,queryBase;
let upValues=[];
var firstVal="物料",priceVal="一箱价格",softVal="软支",hardVal="硬支",costVal="单价",baseVal="基料",opsucess="操作成功",operror="操作失败";;
var btnSave,contextmenu,action;
contextmenu=true;//显示全部右键菜单
//也可使用 ["row_above", "row_below", "undo", "redo"]; 设置自定义菜单
//判断是否为空
function isEmpty(s)
{
if (s == null || s=="undefined" || s.length == 0)
return true;
return !/\S/.test(s);
}
//通过特定文字找到单元格
function SearchKey(val)
{
var search = hot.getPlugin("search");
return search.query(val);
}
function CheckWuliaoIsAtFirst()
{
var chk=false,query=queryFirst;
if(!isEmpty(queryFirst))
{
if(query.length>0)
{
if(query[0].row==0&&query[0].col==0)
{
chk=true;
}
else
{
alert("第1行第1列必须为[物料]");
return false;
}
}
else
{
alert("第1行第1列必须为[物料]");
return false;
}
}
return chk;
}
function CheckSoftOrHard()
{
var newSoftVal,newHardVal;
newSoftVal=querySoft;
newHardVal=queryHard;
if(isEmpty(newSoftVal)||isEmpty(newHardVal))
{
alert("没有软支或硬支一箱价");
return false;
}
else
{
return true;
}
}
//根据第一列选中的物料ID更新单价
function ChangeSelect(row,value,atOnce)
{
var newAry=FindAry(value,formulaDatas);
if(newAry!=-1)
{
var query=queryCost;
var newVal=newAry[1];
var newVal2=newAry[2];
if(query.length>0)
{
var col=query[0].col;
if(atOnce)
{
hot.setDataAtCell(row,col,newVal);
}
else
{
upValues.push(new Array(row,col,newVal));
}
//更新基料列
col=queryBase[0].col;
var celData=hot.getDataAtCell(row,col);
if(isEmpty(celData))//为空则更新基料列
{
if(atOnce)
{
hot.setDataAtCell(row,col,newVal2);
}
else
{
upValues.push(new Array(row,col,newVal));
}
}
}
else
{
if(atOnce)
{
hot.setDataAtCell(row,1,newVal2);
hot.setDataAtCell(row,2,newVal);
}
else
{
upValues.push(new Array(row,1,newVal));
upValues.push(new Array(row,1,newVal));
}
}
}
}
function UpdatePrice(atOnce)
{
var firstCol=hot.getDataAtCol(0);
for(var i=1;i<firstCol.length;i++)
{
var celVal=firstCol[i];
if(!isEmpty(celVal))
{
if(celVal.length>0)
{
ChangeSelect(i,celVal,atOnce);
}
}
}
if(!atOnce)
{
hot.batchRender(() =>
{
for(var i=0;i<upValues.length;i++)
{
hot.setDataAtCell(upValues[i][0],upValues[i][1],upValues[i][2]);
}
});
upValues.length=0;
}
}
function FindAry(target,array)
{
if(!isEmpty(array))
{
var i,j;
for(i=0;i<array.length;i++)
{
for(j=0;j<array[0].length;j++)
{
if(array[i][j]==target)
{
return array[i];
}
}
}
}
return -1;
}
function GetData()
{
var paraData="action="+action;
$.ajax({
type: "POST",
url: "handler.asp",
timeout: 10000,
data:paraData,
cache:false,
dataType:"html",
success:function(res){},
complete:function(res)
{
var result=res.responseText;
if(result.indexOf("[[")>=0)
{
var datas=result.split("$$$"),data1;
if(isEmpty(datas[0])||datas[0].length<10)//如果是空
{
data1=Handsontable.helper.createSpreadsheetData(32,16);//创建一个32行16列的空表
}
else
{
data1=JSON.parse(datas[0]);
}
var Columns;
if(datas[1].length>2)
{
Columns=eval("("+datas[1]+")");
//Columns=JSON.parse(datas[1]);//因为返回的是嵌套JSON不能使用JSON.parse转换
}
if(datas[2].length>2)
{
formulaDatas=JSON.parse(datas[2]);//与第一列联动的名称和单价数组
}
var exceltable = document.getElementById("exceltable");
hot = new Handsontable(exceltable,{
data:data1,//表格中的数据项
colHeaders: true,//当值为true时显示列头,当值为数组时,列头为数组的值
rowHeaders: true,//当值为true时显示行头,当值为数组时,行头为数组的值
contextMenu:contextmenu,//右键菜单
manualColumnResize: true,//调整列大小
allowInsertRow: true,//允许插入行
allowInsertColumn: true,//允许插入列
fillHandle:true,//当值为true时,允许拖动单元格右下角,将其值自动填充到选中的单元格
search:true,//查询单元格的值 查询单元格的值需要3个步骤:a.设置hot的属性search为true b.创建比对函数 c.渲染比对结果
columns:Columns,//列设置
fixedRowsTop:1,//固定第一行
fillHandle:true,//拖动单元格时自动增加新行
language:"zh-CN",//中文
licenseKey:"6f673-f83c5-43005-a9f45-ecab9",//授权码
formulas:true,//使用公式
afterChange:function(changes,source)
{
if (source === "edit")
{
changes.forEach(function (item)
{
var row = item[0],
col = item[1],
prevValue = item[2],
value = item[3];
if(col==0&&prevValue!== value)//改变的是第1列
{
ChangeSelect(row,value,true);
}
});
}
},
afterUpdateSettings:function()
{
queryFirst=SearchKey(firstVal);
queryPrice=SearchKey(priceVal);
querySoft=SearchKey(softVal);
queryHard=SearchKey(hardVal);
queryCost=SearchKey(costVal);
queryBase=SearchKey(baseVal);
UpdatePrice(false);
}
});
if(!hot.isEmptyRow(0))//第一行不为空
{
//设置只读 非编辑基础模板时要设置下
hot.updateSettings(
{
cells:function (row,col,prop)
{
var cellProperties = {};
if(row==0){//0行
if(col==0||col==1||col==3)
{
cellProperties.readOnly = true;
}
}
else if(row==22&&col==5){
cellProperties.readOnly = true;
}
else if(row==17){
if(col==8||col==9)
{
cellProperties.readOnly = true;
}
}
else if(col==3)
{
cellProperties.readOnly = true;
}
//格式化公式
var instance=this.instance;
var value=instance.getSourceDataAtCell(row,col);//好象不能取到原始公式数据
if(!isEmpty(value))
{
if(value[0]=="=")//公式
{
if(isEmpty(cellProperties.className))
{
cellProperties.className = "formula";
}
else
{
cellProperties.className = cellProperties.className+" formula";
}
cellProperties.type="numeric";//必须设置type为numeric才会格式化
cellProperties.numericFormat={pattern:"0.00"};
cellProperties.correctFormat=true;
}
}
return cellProperties;
}
});
}
}
},
error: function(e){}
})
}
function SaveData()
{
queryFirst=SearchKey(firstVal);
queryPrice=SearchKey(priceVal);
querySoft=SearchKey(softVal);
queryHard=SearchKey(hardVal);
queryCost=SearchKey(costVal);
queryBase=SearchKey(baseVal);
var canSave=false,paraData="action=savedata",alldata;
canSave=CheckWuliaoIsAtFirst();//判断是否含有某些特殊内容(正式配方表项目中才会使用到)
if(canSave)
{
canSave=CheckSoftOrHard();//判断是否含有硬支、软支价格(正式配方表项目中才会使用到)
}
if(canSave&&(action=="new"||action=="edit"||action=="copy"))
{
canSave=CheckClass();//判断是否选择了所属分类(正式配方表项目中才会使用到)
if(canSave)
{
canSave=CheckInput();//判断是否输入了配方表名称和编码(正式配方表项目中才会使用到)
}
if(canSave)
{
alldata=hot.getSourceData();//一定要使用getSourceData不能使用getData,否则公式无法保存
}
}
else
{
alldata=hot.getSourceData();//一定要使用getSourceData不能使用getData,否则公式无法保存
hot.loadData(alldata);
}
if(canSave&&!isEmpty(alldata))
{
var hotdata=JSON.stringify(alldata);
var datas=escape(hotdata).replace(/\+/g,"%2B");//将+号转换为%2B
paraData+="&data="+datas;
$.ajax({
type: "POST",
url: "handler.asp",
timeout: 10000,
data:paraData,
cache:false,
dataType:"html",
success:function(res){},
complete:function(res)
{
var txt=res.responseText.split("$$$");
if(txt[0]=="1")
{
alert(opsucess);
}
else
{
alert(txt[1]);
}
},
error:function(e){}
});
}
}
function btnClick()
{
SaveData();
}
$(document).ready(function()
{
action="";
GetData();
btnSave=document.getElementById("btnSave");
Handsontable.dom.addEvent(btnSave,"click",function()
{
SaveData();
});
});
excel.js文件中需要特别注意以下几个地方
A、Handsontable的Setting options中 language 项根据实际需要的语言填写,默认为英文,此项目为 zh-CN
B、Handsontable的Setting options中 licenseKey 项如果不填写或填写错误将会显示如下版权信息(建议商用购买正式版)
C、hot.updateSettings代码串为设置表格的列属性和formula属性
4、编写后端数据处理asp程序(handler.asp),内容如下
<%@LANGUAGE="VBSCRIPT" CODEPAGE="65001"%><!--#include file="conn.asp"--><%Response.Clear()
Response.Expires=0
Response.CharSet="utf-8"
Response.ContentType="text/html"
Response.Buffer=true
Dim data,val,action,result,idx,i,aryLength,dataAry,Columns,dataAry2,formulaDatas,cols,sql,rs,bomCode,bomName,bomPrice,nowid
Dim formulaCode,formulaName,formulaData,softPrice,hardPrice,useType,fromID,sessionName,msg,fatherID
sessionName="handdata"
Set rs=Server.CreateObject("Adodb.RecordSet")
rs.CursorLocation=3
result="0"
action=Request.Form("action")
Select Case action
Case "savedata"
data=Request.Form("data")
idx=InStr(data,":[[""")
if idx>0 then
data=Right(data,Len(data)-idx)
data=Left(data,Len(data)-1)
end if
Call SetadminInfo(sessionName,Trim(data))
result="1"
Case "default",""
Session(sessionName)=""
Call GetadminInfo(sessionName)
data=Session(sessionName)&""
if(Len(data))<10 then
Session("default")=""
Call GetadminInfo("default")
data=Session("default")
end if
if(Len(data))>10 then
dataAry=Split(data,"],[")
dataAry2=Split(dataAry(1),",")
aryLength=UBound(dataAry2)
Call GetformulaDatas()
end if
result=data&""&splitstr3&""&Columns&""&splitstr3&""&formulaDatas
End Select
Set rs=Nothing
conn.Close()
Set conn=Nothing
Response.Write(result)
Response.Flush()
Response.End()
Function Ceil(val)
Dim return
return = int(val)
Cei2=val-return
if Cei2>0 then
Ceil = return + 1
else
Ceil=val+0
End If
End Function
Sub GetformulaDatas()
dataAry=Split(data,"],[")
dataAry2=Split(dataAry(1),",")
aryLength=UBound(dataAry2)
Dim rs1,idx1
idx1=1
cols="["
formulaDatas="["
sql="Select bomCode,bomName,bomPrice From bom"
Set rs1=Server.CreateObject("Adodb.RecordSet")
rs1.Open sql,conn,1,1
Do While Not rs1.Eof
bomCode=rs1("bomCode")
bomName=rs1("bomName")
bomPrice=FormatMoney(rs1("bomPrice"))
if idx1<rs1.RecordCount then
cols=cols&"{""编码"":"""&bomCode&""",""名称"":"""&bomName&""",""单价"":"""&bomPrice&"""},"
formulaDatas=formulaDatas&"["""&bomCode&""","""&bomPrice&""","""&bomName&"""],"
else
cols=cols&"{""编码"":"""&bomCode&""",""名称"":"""&bomName&""",""单价"":"""&bomPrice&"""}"
formulaDatas=formulaDatas&"["""&bomCode&""","""&bomPrice&""","""&bomName&"""]"
end if
idx1=idx1+1
rs1.MoveNext()
Loop
rs1.Close()
Set rs1=Nothing
cols=cols&"]"
formulaDatas=formulaDatas&"]"
Columns="["
for i=0 to aryLength
if i=0 then
Columns=Columns&"{""type"":""handsontable"",""handsontable"":{"
Columns=Columns&"""colHeaders"":[""编码"",""名称"",""单价""],"
Columns=Columns&"""autoColumnSize"":""true"","
Columns=Columns&"data:"&cols&","
Columns=Columns&"getValue:function(){var selection = this.getSelectedLast();return this.getSourceDataAtRow(Math.max(selection[0],0)).编码;}"
Columns=Columns&"}},"
'生成第一列的handsontable联动选择内容
elseif i=2 or i=4 or i=12 then
Columns=Columns&"{""type"":""numeric""},"
elseif i=aryLength then
Columns=Columns&"{}"
else
Columns=Columns&"{},"
end if
next
Columns=Columns&"]"
end Sub
Sub GetadminInfo(username)
Dim sqla,rsa
if Len(Session(username)&"")=0 then
sqla="Select password From admin Where username='"&username&"'"
Set rsa=conn.Execute(sqla)
if not rsa.Eof then
admininfo=rsa(0)
Session(username)=admininfo
end if
Set rsa=Nothing
end if
End Sub
Sub SetadminInfo(username,psw)
Dim sqla,rsa
Set rsa=Server.CreateObject("Adodb.RecordSet")
sqla="Select * From admin Where username='"&username&"'"
rsa.CursorLocation=3
rsa.Open sqla,conn,1,3
if rsa.Eof then
rsa.AddNew()
rsa("username")=username
end if
rsa("password")=psw
if username="batteryidx" or username="group" then
rsa("time")=Now()
end if
rsa.UpDate()
rsa.Close()
Session(username)=psw
Set rsa=Nothing
End Sub
Function FormatMoney(mm)
if mm&""="" then
mm="0"
end if
if MoneyModel="0" then
FormatMoney=FormatNumber(mm,MoneyDecimal,-1,0,0)
else
FormatMoney=FormatNumber(mm,MoneyDecimal,-1,0,-1)
end if
End Function
%>
5、编写数据库连接asp文件(conn.asp),代码如下
<%Dim DBname,ItemRecT,ItemRecF,ItemRecNow,conn,connstr,FuncLen
ItemRecT="True"
ItemRecF="False"
DateStr="'"
Const splitstr3="$$$"
connstr="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Server.MapPath("data/nbyzs#oa.mdb") & ";Persist Security Info=False"
'以下为mssql数据库连接字符串
'connstr="Provider=SQLOLEDB.1;server=.\SQL2005;uid=sa;pwd=88888888;database=luyuan"
Session("ConnStr")=connstr
Set conn=Server.CreateObject("Adodb.Connection")
conn.Open connstr
function connClose
if LCase(TypeName(conn))="connection" then
Conn.Close()
Set conn=Nothing
end if
End Function%>
至此大功告成,速速围观整体效果如下图
如果在此基础上增加每个Excel表格的增加、修改、删除、查看权限控制是不是就特别方便帮办公室小姐姐实现在线excel表格管理功能呢?
这样即能实现数据共享又可以很方便地进行表格管理,还不用担心数据丢失。
本文完整代码下载链接 https://download.csdn.net/download/chenagen/18241896
不过,我已经开发出了完整的这种功能,有需要的可以查看以下演示(Excel配方表系统)
http://jxc.51lego.net/ 账号密码均为 lj