上面的例子,仍然没有取得正确的页面配置信息,在下面的例子中,我们就来设置正确的页面大小和页边距:
取得正确的页面设置
<HTML XMLNS:IE>
<HEAD>
<?IMPORT NAMESPACE="IE" IMPLEMENTATION="#default">
<STYLE TYPE="text/css">
.lorstyle
{
width:5.5in;
height:8in;
margin:1in;
background:white;
}
.pagestyle
{
background:white;
border-left:1 solid black;
border-top:1 solid black;
border-right:4 solid black;
border-bottom:4 solid black;
width:8.5in;
height:11in;
margin:10px;
overflow:hidden;
}
.headerstyle
{
position:absolute;
top:.25in;
width:6in;
left:1in;
}
.footerstyle
{
position:absolute;
top:10.5in;
width:6in;
left:1in;
}
</STYLE>
<SCRIPT LANGUAGE="JScript">
var iNextPageToCreate = 1;
var oPageStyleClass;
var oLorStyleClass;
var oHeaderStyleClass;
var oFooterStyleClass;
// Returns the object corresponding to a named style rule
function FindStyleRule(styleName)
{
for (i = 0; i < document.styleSheets.length; i++)
{
for (j = 0; j < document.styleSheets(i).rules.length; j++)
{
if (document.styleSheets(i).rules(j).selectorText == styleName)
return document.styleSheets(i).rules(j);
}
}
}
function Init()
{
AddFirstPage();
oPageStyleClass = FindStyleRule(".pagestyle");
oLorStyleClass = FindStyleRule(".lorstyle");
oHeaderStyleClass = FindStyleRule(".headerstyle");
oFooterStyleClass = FindStyleRule(".footerstyle");
oPageStyleClass.style.width = printer.pageWidth/100 + "in";
oPageStyleClass.style.height = printer.pageHeight/100 + "in";
oLorStyleClass.style.marginTop = printer.marginTop/100 + "in";
oLorStyleClass.style.marginLeft = printer.marginLeft/100 + "in";
oLorStyleClass.style.width = (printer.pageWidth - (printer.marginLeft + printer.marginRight))/100 + "in";
oLorStyleClass.style.height = (printer.pageHeight - (printer.marginTop + printer.marginBottom))/100 + "in";
oHeaderStyleClass.style.left = printer.marginLeft/100 + "in";
oHeaderStyleClass.style.top = printer.unprintableTop/100 + "in";
oHeaderStyleClass.style.width = oLorStyleClass.style.width;
oFooterStyleClass.style.left = printer.marginLeft/100 + "in";
oFooterStyleClass.style.width = oLorStyleClass.style.width;
oFooterStyleClass.style.bottom = printer.unprintableBottom/100 + "in";
}
function CheckPrint()
{
switch (dialogArguments.__IE_PrintType)
{
case "Prompt":
if (printer.showPrintDialog())
PrintPrep();
break;
case "NoPrompt":
PrintPrep();
break;
case "Preview":
default:
break;
}
}
function AddFirstPage()
{
newHTML = "<IE:DEVICERECT ID='page1' MEDIA='print' CLASS='pagestyle'>";
newHTML += "<IE:LAYOUTRECT ID='layoutrect1' CONTENTSRC='document' ONLAYOUTCOMPLETE='OnRectComplete()' NEXTRECT='layoutrect2' CLASS='lorstyle'/>";
newHTML += "</IE:DEVICERECT>";
pagecontainer.insertAdjacentHTML("afterBegin", newHTML);
headfoot.textHead = printer.header;
headfoot.textFoot = printer.footer;
headfoot.url = dialogArguments.__IE_BrowseDocument.URL;
headfoot.title = dialogArguments.__IE_BrowseDocument.title;
headfoot.page = 1;
AddHeaderAndFooterToPage(1);
iNextPageToCreate = 2;
}
function OnRectComplete()
{
if (event.contentOverflow == true)
AddNewPage();
else
{
headfoot.pageTotal = document.all.tags("DEVICERECT").length;
for (i = 1; i <= headfoot.pageTotal; i++)
AddPageTotalToPages(i);
setTimeout("CheckPrint();", 100);
}
}
function AddNewPage()
{
document.all("layoutrect" + (iNextPageToCreate - 1)).onlayoutcomplete = null;
headfoot.page = iNextPageToCreate;
newHTML = "<IE:DEVICERECT ID='page" + iNextPageToCreate + "' MEDIA='print' CLASS='pagestyle'>";
newHTML += "<IE:LAYOUTRECT ID='layoutrect" + iNextPageToCreate + "' ONLAYOUTCOMPLETE='OnRectComplete()' NEXTRECT='layoutrect" + (iNextPageToCreate + 1) + "' CLASS='lorstyle'/>";
newHTML += "</IE:DEVICERECT>";
pagecontainer.insertAdjacentHTML("beforeEnd", newHTML);
AddHeaderAndFooterToPage(iNextPageToCreate);
iNextPageToCreate++;
}
function AddHeaderAndFooterToPage(pageNum)
{
newHeader = "<DIV CLASS='headerstyle'>" + headfoot.htmlHead + "</DIV>";
newFooter = "<DIV CLASS='footerstyle'>" +headfoot.htmlFoot + "</DIV>";
document.all("page" + pageNum).insertAdjacentHTML("afterBegin", newHeader);
document.all("page" + pageNum).insertAdjacentHTML("beforeEnd", newFooter);
}
function AddPageTotalToPages()
{
oSpanCollection = document.all.tags("span");
for (i = 0; i < oSpanCollection.length; i++)
{
if (oSpanCollection[i].className == "hfPageTotal")
oSpanCollection[i].innerText = headfoot.pageTotal;
}
}
function PrintPrep()
{
if (layoutrect1.contentDocument.readyState == "complete")
{
// This block will be called when printing with user prompt
// because the Print dialog box gives time for the content
// document to complete loading
PrintNow();
}
else
{
// This block will usually be called when printing w/o user prompt
// and sets an event handler that listens for the loading of the content
// document before printing. Sometimes, however, the content document
// will be loaded in time for the previous block to execute
layoutrect1.contentDocument.onreadystatechange = PrintWhenContentDocComplete;
}
}
function PrintWhenContentDocComplete()
{
if (layoutrect1.contentDocument.readyState == "complete")
{
layoutrect1.contentDocument.onreadystatechange = null;
PrintNow();
}
}
function PrintNow()
{
var startPage;
var endPage;
var oDeviceRectCollection = document.all.tags("DEVICERECT");
if (dialogArguments.__IE_PrintType == "NoPrompt" ||
printer.selectedPages == false)
{
startPage = 1;
endPage = oDeviceRectCollection.length;
}
else if (printer.currentPage == true)
{
// Determine current page, if necessary.
// Normally, the print current page option is disabled because a print
// template has no way of determining which section of a document is displayed
// in the browser and cannot calculate a current page. In print preview,
// a print template can enable the print current page option because the
// template can determine which page is currently showing in the print
// preview dialog. The print preview window would need a user interface
// with a print button so that the user could print from the print preview
// window. See template7.htm for an example showing how to add a user
// interface to a print preview template.
}
else
{
startPage = printer.pageFrom;
endPage = printer.pageTo;
if (startPage > endPage)
{
alert("Error: Start page greater than end page");
return;
}
if (startPage > oDeviceRectCollection.length)
{
alert("Error: Start page greater than number of pages in print job.");
return;
}
if (endPage > oDeviceRectCollection.length)
{
alert("Warning: End page greater than number of pages in print job. Continuing Print Job.");
endPage = oDeviceRectCollection.length;
}
}
printer.startDoc("Printing from Tmplt6.htm");
for (i = startPage - 1; i < endPage; i++)
printer.printPage(oDeviceRectCollection[i]);
printer.stopDoc();
}
</SCRIPT>
</SCRIPT>
<HEAD>
<BODY ONLOAD="Init()">
<IE:TEMPLATEPRINTER ID="printer"/>
<IE:HEADERFOOTER ID="headfoot"/>
<DIV ID="pagecontainer">
<!-- Dynamically created pages go here. -->
</DIV>
</BODY>
</HTML>
效果如下图:现在已经是正确的设置了。
本模板集成了前面所有的模板,并增强了打印支持。在以前的例子中,为了简单起见,我们假设了纸张的大小,页边距,非打印区域,被打印的页面等等。本模板就读取那些存储在“页面设置”和打印对话框中大部分实际的值来格式化打印和预 览的页面,并仅仅打印用户希望的页面。一定要看看Init函数,样式表在这里被设置。因为打印机的计算单位是1/100英寸,所以,我们必须在使用前把这些值转换为英寸:除以100并在后面加上字符串“in”。
最后,我们开始最cool的技术,在打印预览街面上放置我们自己的UI控制界面:
定义自己的UI
<HTML XMLNS:IE>
<HEAD>
<?IMPORT NAMESPACE="ie" IMPLEMENTATION="#default">
<STYLE TYPE="text/css">
.lorstyle
{
background:white;
margin-top:1in;
margin-left:1in;
width:6in;
height:8in;
}
.pagestyle
{
background:white;
border-left:1 solid black;
border-top:1 solid black;
border-right:4 solid black;
border-bottom:4 solid black;
width:8.5in;
height:11in;
margin:10px;
overflow:hidden;
left:-100in;
}
.headerstyle
{
position:absolute;
top:.25in;
width:6in;
left:1in;
}
.footerstyle
{
position:absolute;
top:10.5in;
width:6in;
left:1in;
}
#ui
{
height:60px;
background-color:red;
margin:0px;
padding;0px;
}
#zoomcontainer
{
zoom:50%;
width:100%;
position:relative;
}
#pagecontainer
{
width:100%;
overflow:auto;
border:'thin threedhighlight inset';
background:threedshadow;
}
</STYLE>
<SCRIPT LANGUAGE="JScript">
var iNextPageToCreate = 1;
var oPageStyleClass;
var oLorStyleClass;
var oHeaderStyleClass;
var oFooterStyleClass;
function FindStyleRule(styleName)
{
for (i = 0; i < document.styleSheets.length; i++)
{
for (j = 0; j < document.styleSheets(i).rules.length; j++)
{
if (document.styleSheets(i).rules(j).selectorText == styleName)
return document.styleSheets(i).rules(j);
}
}
}
function InitClasses()
{
oPageStyleClass.style.width = printer.pageWidth/100 + "in";
oPageStyleClass.style.height = printer.pageHeight/100 + "in";
oLorStyleClass.style.marginTop = printer.marginTop/100 + "in";
oLorStyleClass.style.marginLeft = printer.marginLeft/100 + "in";
oLorStyleClass.style.width = (printer.pageWidth - (printer.marginLeft + printer.marginRight))/100 + "in";
oLorStyleClass.style.height = (printer.pageHeight - (printer.marginTop + printer.marginBottom))/100 + "in";
oHeaderStyleClass.style.left = printer.marginLeft/100 + "in";
oHeaderStyleClass.style.top = printer.unprintableTop/100 + "in";
oHeaderStyleClass.style.width = oLorStyleClass.style.width;
oFooterStyleClass.style.left = printer.marginLeft/100 + "in";
oFooterStyleClass.style.width = oLorStyleClass.style.width;
oFooterStyleClass.style.top = null;
oFooterStyleClass.style.bottom = printer.unprintableBottom/100 + "in";
}
function Init()
{
AddFirstPage();
zoomcontainer.style.zoom = "50%";
ui.style.width = document.body.clientWidth;
ui.style.height = "60px";
pagecontainer.style.height = document.body.clientHeight - ui.style.pixelHeight;
oPageStyleClass = FindStyleRule(".pagestyle");
oLorStyleClass = FindStyleRule(".lorstyle");
oHeaderStyleClass = FindStyleRule(".headerstyle");
oFooterStyleClass = FindStyleRule(".footerstyle");
InitClasses();
}
function CheckPrint()
{
switch (dialogArguments.__IE_PrintType)
{
case "Prompt":
if (printer.showPrintDialog())
PrintPrep();
break;
case "NoPrompt":
PrintPrep();
break;
case "Preview":
default:
break;
}
}
function PrintPrep()
{
if (layoutrect1.contentDocument.readyState == "complete")
{
// This block will be called when printing with user prompt
// because the Print dialog box gives time for the content
// document to complete loading
PrintNow();
}
else
{
// This block will usually be called when printing w/o user prompt
// and sets an event handler that listens for the loading of the content
// document before printing. Sometimes, however, the content document
// will be loaded in time for the previous block to execute
layoutrect1.contentDocument.onreadystatechange = PrintWhenContentDocComplete;
}
}
function PrintWhenContentDocComplete()
{
if (layoutrect1.contentDocument.readyState == "complete")
{
layoutrect1.contentDocument.onreadystatechange = null;
PrintNow();
}
}
function PrintNow()
{
var startPage;
var endPage;
var oDeviceRectCollection = document.all.tags("DEVICERECT");
if (dialogArguments.__IE_PrintType == "NoPrompt" ||
printer.selectedPages == false)
{
startPage = 1;
endPage = oDeviceRectCollection.length;
}
else if (printer.currentPage == true)
{
// Determine current page, if necessary.
// Normally, the print current page option is disabled because a print
// template has no way of determining which section of a document is displayed
// in the browser and cannot calculate a current page. In print preview,
// a print template can enable the print current page option because the
// template can determine which page is currently showing in the print
// preview dialog. The print preview window would need a user interface
// with a print button so that the user could print from the print preview
// window. See template7.htm for an example showing how to add a user
// interface to a print preview template.
}
else
{
startPage = printer.pageFrom;
endPage = printer.pageTo;
if (startPage > endPage)
{
alert("Error: Start page greater than end page");
return;
}
if (startPage > oDeviceRectCollection.length)
{
alert("Error: Start page greater than number of pages in print job.");
return;
}
if (endPage > oDeviceRectCollection.length)
{
alert("Warning: End page greater than number of pages in print job. Continuing Print Job.");
endPage = oDeviceRectCollection.length;
}
}
printer.startDoc("Printing from Tmplt6.htm");
for (i = startPage - 1; i < endPage; i++)
printer.printPage(oDeviceRectCollection[i]);
printer.stopDoc();
}
function AddFirstPage()
{
newHTML = "<IE:DEVICERECT ID='page1' MEDIA='print' CLASS='pagestyle'>";
newHTML += "<IE:LAYOUTRECT ID='layoutrect1' CONTENTSRC='document' ONLAYOUTCOMPLETE='OnRectComplete()' NEXTRECT='layoutrect2' CLASS='lorstyle'/>";
newHTML += "</IE:DEVICERECT>";
zoomcontainer.insertAdjacentHTML("afterBegin", newHTML);
headfoot.textHead = printer.header;
headfoot.textFoot = printer.footer;
headfoot.url = dialogArguments.__IE_BrowseDocument.URL;
headfoot.title = dialogArguments.__IE_BrowseDocument.title;
headfoot.page = 1;
AddHeaderAndFooterToPage(1);
iNextPageToCreate = 2;
}
function OnRectComplete()
{
if (event.contentOverflow == true)
AddNewPage();
else
{
headfoot.pageTotal = document.all.tags("DEVICERECT").length;
for (i = 1; i <= headfoot.pageTotal; i++)
AddPageTotalToPages(i);
setTimeout("CheckPrint();", 100);
}
}
function OnRectCompleteSimple()
{
if (event.contentOverflow == true) return;
headfoot.pageTotal = parseInt(event.srcElement.parentElement.id.substring(4), 10);
AddPageTotalToPages();
ShowFilledPagesAndHideExcessPages();
}
function ShowFilledPagesAndHideExcessPages()
{
for (i = 1; i <= headfoot.pageTotal; i++)
document.all("page" + i).style.position = "static";
var i = headfoot.pageTotal + 1;
while (document.all("page" + i))
{
document.all("page" + i).style.position = "absolute";
i++;
}
}
function AddHeaderAndFooterToPage(pageNum)
{
newHeader = "<DIV CLASS='headerstyle'>" + headfoot.htmlHead + "</DIV>";
newFooter = "<DIV CLASS='footerstyle'>" +headfoot.htmlFoot + "</DIV>";
document.all("page" + pageNum).insertAdjacentHTML("afterBegin", newHeader);
document.all("page" + pageNum).insertAdjacentHTML("beforeEnd", newFooter);
}
function AddNewPage()
{
document.all("layoutrect" + (iNextPageToCreate - 1)).onlayoutcomplete = OnRectCompleteSimple;
headfoot.page = iNextPageToCreate;
newHTML = "<IE:DEVICERECT ID='page" + iNextPageToCreate + "' MEDIA='print' CLASS='pagestyle'>";
newHTML += "<IE:LAYOUTRECT ID='layoutrect" + iNextPageToCreate + "' ONLAYOUTCOMPLETE='OnRectComplete()' NEXTRECT='layoutrect" + (iNextPageToCreate + 1) + "' CLASS='lorstyle'/>";
newHTML += "</IE:DEVICERECT>";
zoomcontainer.insertAdjacentHTML("beforeEnd", newHTML);
AddHeaderAndFooterToPage(iNextPageToCreate);
headfoot.pageTotal = iNextPageToCreate;
iNextPageToCreate++;
}
function AddPageTotalToPages()
{
oSpanCollection = document.all.tags("span");
for (i = 0; i < oSpanCollection.length; i++)
{
if (oSpanCollection[i].className == "hfPageTotal")
oSpanCollection[i].innerText = headfoot.pageTotal;
}
}
function ResizeApp()
{
ui.style.width = document.body.clientWidth;
pagecontainer.style.height = document.body.clientHeight - ui.style.pixelHeight;
}
function Zoomer(string)
{
if (string == "in")
{
currZoom = zoomcontainer.style.zoom;
currZoom = currZoom.substring(0, currZoom.length - 1);
currZoom = parseInt(currZoom, 10);
newZoom = currZoom + 1;
if (newZoom > 10000) return;
zoomcontainer.style.zoom = newZoom + "%";
zoomnumber.value = newZoom;
}
else if (string == "out")
{
currZoom = zoomcontainer.style.zoom;
currZoom = currZoom.substring(0, currZoom.length - 1);
currZoom = parseInt(currZoom, 10);
newZoom = currZoom - 1;
if (newZoom < 1) return;
zoomcontainer.style.zoom = newZoom + "%";
zoomnumber.value = newZoom;
}
else
{
if (event.srcElement.id != "zoomnumber") return;
if (event.keyCode != 13) return;
var newZoom = zoomnumber.value;
numValue = parseInt(newZoom, 10);
if (numValue != newZoom) return;
if (newZoom > 10000) return;
if (newZoom < 1) return;
zoomcontainer.style.zoom = newZoom + "%";
}
}
function DoPageSetup()
{
printer.showPageSetupDialog();
InitClasses();
}
function DoPrintFromPreview()
{
if (printer.showPrintDialog())
PrintPrep();
}
</SCRIPT>
<HEAD>
<BODY ONLOAD="Init()" ONRESIZE="ResizeApp()" SCROLL="no">
<IE:TEMPLATEPRINTER ID="printer"/>
<IE:HEADERFOOTER ID="headfoot"/>
<DIV ID="ui">
<BR>
<INPUT TYPE="BUTTON" VALUE="Page Setup" onmouseup="DoPageSetup()">
<INPUT TYPE="BUTTON" VALUE="Zoom In" onmouseup="Zoomer('in')">
<INPUT ID="zoomnumber" TYPE="TEXT" VALUE="50" SIZE="3" MAXLENGTH="4" onkeyup="Zoomer('amount')">%
<INPUT TYPE="BUTTON" VALUE="Zoom Out" onmouseup="Zoomer('out')">
<INPUT TYPE="BUTTON" VALUE="Print" onmouseup="DoPrintFromPreview()">
</DIV>
<DIV ID="pagecontainer">
<DIV ID="zoomcontainer">
<!-- Dynamically created pages go here. -->
</DIV>
</DIV>
</BODY>
</HTML>
效果如下图:
本例演示了如何生成你自己的打印预览用户接口。在本例中的用户接口允许用户访问页面设置和打印对话框,并且可以让用户缩放文档。为了建立用户接口,把BODY元素的SCROLL属性设置成了“no”,这样可以防止出现滚动条。“ui”、“div”是一个固定的,静态的区域,里面包含用户接口的按钮和文本框。ID为pagecontainer的DIV元素定义了内容页面显示的区域。他的样式表把他的overflow属性设置成了“auto”,这样的话,滚动条就会出现在他的上面(在需要的时候)而不是 BODY元素的上面。这样,用户就可以滚动到任何用户想看的页面而不用移动位于屏幕上方的用户接口按钮。一个带有用户接口的打印模板必须响应页面设置和打印设置的更改。首先要考虑的是当用户把页面尺寸变大的时候(例如把letter变成了legal纸)如何处理不在需要的LAYOUTRECT和DEVICERECT,并减少文档的页数。在当前打印模板的实现中,一旦LAYOUTRECT被生成了,一个模板就不要销毁他们,隐藏这些不需要的LAYOUTRECT是一个更好的方法。你有许多的方法来处理这个问题,一个有效的方法就是用绝对定位在屏幕隐上藏这些元素而不不要使用display属性。这些没有显示的LAYOUTRECT不会调用onlayoutcomplete事件。
在本模板中,class为.pagestyle的DEVICERECT元素具有一个left属性,他的值是-50英寸,该属性会被忽略,如果position属性没有设置或者设置为“static”。因此当一个DEVICERECT元素的positioin属性设置为“absolute”的时候,该页就会从屏幕上消失(因为他的left=-50in)。模板添加了一个函数——ShowFilledPagesAndHideExcessPages——它根据headfoot.pageTotal的值设置所有DEVICERECT的position属性为“static”或“absolute”,不管什么时候,如果conentOverflow的值在onlayoutcomplete事件处理函数中为false,那么headfoot.pageTotal值就会被设置,ShowFilledPagesAndHideExcessPages也会被调用。OnRectComplete在本质上仍然与前面所示模板的功能一样,是onlayoutcomplete事件的处理函数,但有所不同,它仅是最后一个LAYOUTRECT的onlayoutcomplete事件的处理函数,对于其他的LAYOUTRECT,只有在第一次生成的时候才是,一旦这些LAYOUTRECT生成了,他们的onlayoutcomplete事件处理函数就在AddNewPage函数被切换成OnRectCompleteSimple。当contentOverflow为true的时候,OnRectCompleteSimple不会增加新的页面。
DoPageSetup函数是Page Setup 按钮的onmouseup事件的处理。当该按钮被按下的时候,页面设置对话框就会出现,.pagestyle,.lorstyle,.headerstyle和.footerstyle这4个class都会根据变化重新初始化。注意页面的排版会在这些class做实际改变的时候重新组织(这会导致onlayoutcomplete事件)。
为了使得用户在打印预览的时候可以缩放页面的大小,本模板增加了一个函数——Zoomer。Zoomer是Zoom In 和Zomm Out按钮的onmouseup事件和文本框的onkeyup事件处理程序。当用户单击任何一个按钮或者更改zoom文本框的值的时候,它更改ID为zoomcontainer的DIV元素的zoom属性(初始值是50%)。
DoPrintFromPreview函数是Print按钮的的onmouseup处理句柄,他只是简单的显示打印对话框,并调用与前一个例子中一样的PrintPrep函数。
现在看来,似乎我们可以完全控制打印了,其实不然,比如我们现在就不可以控制纸张的大小,根据MSDN的说法,可以根据__IE_PrinterCmd_DevMode属性获取打印机的设置,该属性的值是一个DEVMODE结构,通过更改该结构的值就可以控制纸张了。而实际上,无论在什么时候,什么地方,在我们的模板中,该属性的值都是null,其他的属性比如__IE_PrinterCmd_DevNames也是null,看来,MS已经把这些与硬件相关的东西屏蔽掉了。要想真正的做到“完全”控制,可能需要写一个HOOK了,但是这个已经超出了本文讨论的范围,而且,我本人对与VC也不是很熟练,也就不在这献丑了。
这又长又臭的Web打印文章终于完成了,感谢各位的光临,让高手见笑了。
另有朋友问我如何不显示打印对话框而直接打印,那就是把IOleCommandTarget的Exec的第3个参数设置为OLECMDEXECOPT_DONTPROMPTUSER。建议大家在没事的时候,多多看看MSDN(^_^)。
至于用ActiveX进行打印,我在这里简单的讲一下原理,用VC开发最简单,建立一个ATL项目,定义好方法和属性,把资源模板打包进项目,然后把模板的路径变为“res://xxx.dll/模板的名字.htm”就可以了。当然也可以用Delphi等实现。我没有用过VB,应该用VB也是可以实现的吧。至于如何把ActiveX控件的值传入模板,我采用的方法就是现在模板中定义好参数的格式化字符串,把在dll中的模板资源读入内存,替换掉相应参数的格式化字符串,然后把新的内容存入一个临时文件,然后把模板的路径指向该临时文件。
注:本系列文章中的模板代码,都出自MSDN,我仅对某些模板的注释进行了一些翻译或者对模板进行了一些解释。版权不归我,请在用上述代码进行商业用途而被人起诉的时候不要找我(^_^)。