2023年2月

在基于Bootstrap开发的项目中,鲜艳颜色的按钮,以及丰富的图表是很吸引人的特点,为了将这个特点发挥到极致,可以利用Bootstrap图标抽取到数据库里面,并在界面中进行管理和使用,这样我们可以把这些图标方便应用在各个页面部件上,如菜单模块,按钮界面,表单输入等多个场合进行使用。在前面随笔《
基于Metronic的Bootstrap开发框架经验总结(4)--Bootstrap图标的提取和利用
》中,我对如何抽取Bootstrap图标,并单独开发一个页面进行图表的管理,本随笔介绍如何在这个基础上进行优化,实现更方便的使用。

1、优化图标管理界面

在前面随笔中,我们介绍了对图标的抽取、图标管理界面等内容。我们再来回顾一下Bootstrap的图表类型,Bootstrap图标库里面分为了三类内容:

Font Awesome
:Bootstrap专用图标字体,Font Awesome 中包含的所有图标都是矢量的,也就可以任意缩放,避免了一个图标做多种尺寸的麻烦。CSS对字体可以设置的样式也同样能够运用到这些图标上了。

Simple Icons
:收集众多网站的Logo,并提供高质量、不同尺寸的png格式图片给广大网友,所有Icon版权归其所属公司。

Glyphicons
:包括200个符号字体格式图表集合,由Glyphicons提供,Glyphicons Halflings 一般是收费的,但是经过Bootstrap和Glyphicons作者之间的协商,允许开发人员不需要支付费用即可使用。

我们从样式表中抽取这几类图标信息,放到数据库里面,然后方便界面管理和选择处理。

在前面随笔介绍我的Bootstrap框架的时候,图标管理界面如下所示。

选择图标的时候,提供一个弹出的对话框显示分类不同的图标,让用户选择后返回即可。

虽然有了这些功能界面,能够降低我们寻找图标的过程,但是实际使用的时候,还是有一些不方便,因为寻找一个特定的图标,需要翻了很多页才能寻找到合适的,效率不高,通过了解我们自身的图标名称和它显示的内容还是有很大的关联关系的,因此我们应该提供一个显示名称的搜索,方便查询出来,并可以在查询列表中进行选择,这样就可以大大加快我们寻找Bootstrap图标的速度了。

这个界面比原来改进了很多,我们可以通过名称搜索,并获取数据库里面符合条件的图标进行分页显示,如果选择其中之一,可以把图标和名称显示在上面,这样可以方便我们使用。

查询的操作和其他分页的部分类似,把数据通过AJax方式获取后,在界面上进行分页显示即可。

                            <divclass="form-group">
                                <labelclass="control-label col-md-2">图标显示名称</label>
                                <divclass="input-icon col-md-3">
                                    <divclass="input-group">
                                        <divclass="input-icon ">
                                            <inputid="WHC_DisplayName"name="WHC_DisplayName"type="text"class="form-control"placeholder="显示名称...">
                                        </div>
                                        <spanclass="input-group-btn">
                                            <buttonid="btnSearch"class="btn btn-success"type="button"onclick="SearchDisplayName()">
                                                <iclass="glyphicon glyphicon-list"></i>查询</button>
                                        </span>
                                    </div>
                                </div>
                            </div>
        //根据名称查询图标
        functionSearchDisplayName()
{
var condition = "WHC_DisplayName=" + $("#WHC_DisplayName").val();
SearchCondition(currentPage, condition);
}
//图标查询 functionSearch(page) {var condition = "WHC_SourceType=SimpleLine";//SimpleLine,FontAwesome,Glyphicons SearchCondition(page, condition);
}
functionSearchCondition(page, condition) {var iconUrl = "/BootstrapIcon/FindWithPager?page=" + page + "&rows=" +rows;

$.getJSON(iconUrl
+ "&" + condition, function(data) {
$(
"#grid_body").html("");
$.each(data.rows,
function(i, item) {var tr = "<a href='javascript:;' onclick=\"SetIconClass('" + item.ClassName + "')\" class='icon-btn' title=\"" + item.DisplayName + "\">";
tr
+= " <i class=\"" + item.ClassName + " \" style=\"font-size: 2.2em\"></i>";// tr += "</a>";
$(
"#grid_body").append(tr);
});
var element = $('#grid_paging');if(data.total > 0) {var options ={
bootstrapMajorVersion:
3,
currentPage: page,
numberOfPages: rows,
totalPages: Math.ceil(data.total
/rows),
onPageChanged:
function(event, oldPage, newPage) {
SearchCondition(newPage, condition);
}
}
element.bootstrapPaginator(options);
}
else{
element.html(
"");
}
});
}

另外图标的样式我们也方便一起整合让它显示,如Primary Success Info Warning Danger等经典样式,当然我们也可以设置颜色使图标呈现更多的色彩。

相关的界面代码如下所示。

                                <divclass="input-icon col-md-6">
                                    <buttontype="button"class="btn btn-default"onclick="ChangeIconStyle('default')">Default</button>
                                    <buttontype="button"class="btn btn-primary"onclick="ChangeIconStyle('primary')">Primary</button>
                                    <buttontype="button"class="btn btn-success"onclick="ChangeIconStyle('success')">Success</button>
                                    <buttontype="button"class="btn btn-info"onclick="ChangeIconStyle('info')">Info</button>
                                    <buttontype="button"class="btn btn-warning"onclick="ChangeIconStyle('warning')">Warning</button>
                                    <buttontype="button"class="btn btn-danger"onclick="ChangeIconStyle('danger')">Danger</button>
                                </div>
    <script>
        //通过JS修改界面图标的显示和样式
        functionChangeIconStyle(style) {varicon=$("#WebIcon").val();if(style!= 'default') {
$(
"#i_WebIcon").attr("class", icon+ "icon-state-" +style);
$(
"#lbl_WebIcon").attr("class","label label-" +style);
$(
"#lbl_WebIcon").text(icon+ "icon-state-" +style);
}
else{
$(
"#i_WebIcon").attr("class", icon);
$(
"#lbl_WebIcon").attr("class","");
$(
"#lbl_WebIcon").text(icon);
}
}
</script>

2、图标的应用场景

有了这种方便选择图标的管理界面,可以极大提高我们的效率。我们可以在菜单、按钮等多个地方使用图标,使得界面更加美观。

如在菜单界面中使用如下所示。

或者可以左侧菜单进行使用。

我们还可以把图标用在界面的功能按钮上。

如果感兴趣Bootstrap开发框架系列,可以参考学习下面的文章,感谢您的阅读。

在MVC控制器里面使用dynamic和ExpandoObject,实现数据转义的输出

在我们开发微信页面的时候,需要大量用到了各种呈现的效果,一般可以使用Boostrap的效果来设计不同的页面,不过微信团队也提供很多这方面的资源,包括JSSDK的接口,以及Weui的页面样式和相关功能页面,给我们提供了很大的便利,本文是在自己做的一些H5微信应用页面上做了一些功能总结,希望能够给大家提供一定的帮助。

1、微信网页开发

1) JSSDK

微信JS-SDK是
微信公众平台
面向网页开发者提供的基于微信内的网页开发工具包。通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照、选图、语音、位置等手机系统的能力,同时可以直接使用微信分享、扫一扫、卡券、支付等微信特有的能力,为微信用户提供更优质的网页体验。

目前JSSDK支持的接口分类包括下面几类:基础接口、分享接口、图像接口、音频接口、智能接口、设备信息、地理位置、摇一摇周边、界面操作、微信扫一扫、微信小店、微信卡券、微信支付,随着微信功能的全部整合,估计更多的接口会陆续开放出来。

2)WeUI和Jquery WeUI

WeUI
是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页开发量身设计,可以令用户的使用感知更加统一。在微信网页开发中使用 WeUI,有如下优势:

  • 同微信客户端一致的视觉效果,令所有微信用户都能更容易地使用你的网站
  • 便捷获取快速使用,降低开发和设计成本
  • 微信设计团队精心打造,清晰明确,简洁大方

该样式库目前包含 button、cell、dialog、progress、toast、article、icon 等各式元素,已经在
GitHub
上开源。访问
http://weui.github.io/weui/
或微信扫码即可预览。

jQuery WeUI
中使用的是官方 WeUI 的 CSS 代码,并提供了 jQuery/Zepto 版本的 API 实现。JQuery WeUI相对于在官方WeUI的基础上做了一些功能扩展,已丰富界面设计和相关功能,因此我们可以考虑直接基于JQuery  WeUI的基础上进行页面开发即可。

在我前面的一些案例中,都利用了We UI样式来进行很多微信H5页面的功能设计,包括微信支付页面、签到页面等等。

如微信支付页面如下所示:



以及签到页面效果如下所示。


当然我们可以根据业务需要,增加很多这样和微信色调样式一致的页面,这个就是利用WeUI样式带来的界面体验一致性的好处。

本篇主要介绍微信H5页面开发的经验总结,上面提到了利用JSSDK和WeUI来对微信应用的H5页面进行开发,因此下面的相关效果也就是利用这些技术进行处理的。

2、判断微信浏览器

有些情况下,我们可能需要用户只能在微信浏览器上打开,不能用其他浏览器去打开连接,还有就是基于一些用户身份信息的获取,也是需要通过微信浏览器才能重定向获取的,否则使用其他浏览器会出错,因此判断是否为微信浏览器有时候也是一个常规的做法。

判断是否为微信浏览器有两种方式可以达到目的,一个是在前端使用JS脚本去处理,一个是采用后台C#代码进行判断处理,两种均可以达到目的。

使用JS代码实现代码和效果如下所示。

    //判断是否在微信中打开
    functionisWeiXin() {var ua =window.navigator.userAgent.toLowerCase();if (ua.match(/MicroMessenger/i) == 'micromessenger') {return true;
}
else{return false;
}
}

在页面输出处理如下所示。

        $(function() {var isWeixin =isWeiXin();if(isWeixin) {
$(
"#weixin").html(window.navigator.userAgent); //请在微信中打开该页面 }var display = "是否在微信浏览器中打开:";
display
+= isWeixin ? "是" : "否";
$(
"#isWeixin").html(display);
});

如果是正常使用微信跳转的页面链接,那么上会提示为:是。

刚才提到了,使用C#后台代码也可以判断是否在浏览器内,一般情况下,我们可以 判断用户的浏览器后做重定向,如果用户确实是微信浏览器的,则继续后面处理,否则重定向到提示页面给用户。

        /// <summary>
        ///检查是否微信中打开,否则重定向/// </summary>
        /// <returns></returns>
        protected stringCheckBrowser()
{
bool passed = false;string userAgent =Request.UserAgent;

passed
= userAgent.ToLower().Contains("micromessenger");if (!passed)
{
var type = "warn";var message = "请在微信中打开该页面";var url = string.Format("{0}/H5/info?type={1}&message={2}", ConfigData.WebsiteDomain, type, message);returnurl;
}
return null;
}

我们就可以在函数开始部分进行判断即可。

                //如果不是微信浏览器,则返回错误页面
                var checkUrl =CheckBrowser();if (!string.IsNullOrEmpty(checkUrl)) return Redirect(checkUrl);

如果非微信浏览器打开页面链接,重定向的页面效果如下所示。

3、字典数据的绑定

和常规网页功能一样,我们在设计微信页面应用的时候,很多数据也是来源字典数据的,而且需要把它们动态绑定在页面上,微信页面的JQuery WeUI提供了一些列表字典数据的展示效果如下所示。

这个常规的数据绑定如下所示,如下JS代码所示。

$("#job").select({
title:
"选择职业",
items: [
"法官", "医生", "猎人", "学生", "记者", "其他"],
onChange:
function(d) {
console.log(
this, d);
},
onClose:
function() {
console.log(
"close");
},
onOpen:
function() {
console.log(
"open");
},
});

也可以使用集合对象进行赋值处理,如下JS代码所示。

    $("#in").select({
title:
"您的爱好",
multi:
true,
min:
2,
max:
3,
items: [
{
title:
"画画",
value:
1,
description:
"额外的数据1"},
{
title:
"打球",
value:
2,
description:
"额外的数据2"}
],
beforeClose:
function(values, titles) {if(values.indexOf("6") !== -1) {
$.toast(
"不能选打球", "cancel");return false;
}
return true;
},
onChange:
function(d) {
console.log(
this, d);
}
});

基于上面的JS脚本,我们选择后者,使用Ajax技术来填充数据,这样可以动态获取后台的字典数据,并进行页面的绑定操作。

为了方便,我们可以设计一个公共函数,用于数据字典的绑定处理,如下所示。

            //绑定字典内容到指定的控件
            functionBindDictItem(ctrlName, dictTypeName, onChange, onClose, onOpen) {var url = '/h5/GetDictJson?dictTypeName=' +encodeURI(dictTypeName);//获取Ajax的内容,并放到items集合
                var control = $('#' +ctrlName);var items =[];
$.ajax({
type:
'GET',
url: url,
//async: false, //同步 dataType: 'json',
success:
function(data) {
control.empty();
//清空下拉框 //把JSON集合加到数组里面 $.each(data, function(i, item) {
items.push({
title: item.Text, value: item.Value
});
});
//设置显示列表 control.select({
title:
"选择" +dictTypeName,
items: items,
onChange: onChange,
onClose: onClose,
onOpen: onOpen
});
},
error:
function(xhr, status, error) {
$.toast(
"操作失败" + xhr.responseText); //xhr.responseText }
});
}

那么我们绑定字典数据,就只需要调用这个函数就可以很简单实现数据字典的绑定操作了。

            $(function() {
BindDictItem(
"Status", "设备状态");
BindDictItem(
"Dept", "科室");
BindDictItem(
"Building", "建筑物");
BindDictItem(
"Floor", "楼层");
BindDictItem(
"Special", "特殊情况");
});

我们看具体在微信中打开对应连接,字典绑定的效果如下所示。

我们则可以在微信后台对数据字典进行维护即可进行实时的数据更新。


4、微信图片的预览功能

在很多页面里面,我们需要展示丰富的图片,我们需要结合微信的图片预览功能接口,我们才能把图片打开后方便进行缩放处理操作,那么该如何利用微信JSSDK的图片预览接口呢?

首先我们需要引入Jquery WeUI的样式类库,以及JSSDK所需的JS文件,如下所示。

    <scriptsrc="~/Content/wechat/jquery-weui/lib/jquery-2.1.4.js"></script>
    <scriptsrc="~/Content/wechat/jquery-weui/js/jquery-weui.js"></script>
    <scripttype="text/javascript"src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>

然后在页面初始化JSSDK的API脚本,如下代码所示。

        var appid = '@ViewBag.appid';var noncestr = '@ViewBag.noncestr';var signature = '@ViewBag.signature';var timestamp = '@ViewBag.timestamp';

wx.config({
debug:
false,
appId: appid,
//必填,公众号的唯一标识 timestamp: timestamp, //必填,生成签名的时间戳 nonceStr: noncestr, //必填,生成签名的随机串 signature: signature, //必填,签名,见附录1 jsApiList: ['checkJsApi','chooseImage','previewImage','uploadImage','downloadImage']
});
//所有准备好后 wx.ready(function() {
});

加入我们页面里面包含有两部分的图片,一个是设备铭牌图片,一个是其他附属图片,我们分别展示,如下HTML代码所示。

        <divclass="weui_cells_title"><h3>铭牌图片</h3></div>
        <divclass="weui_cells">
            <divclass="weui_cell">
                <divid="previewImage">@for (var i = 0; i<ViewBag.mainList.Count; i++)
{
<img id
='mainPic_@i'src='@ViewBag.mainList[i]'alt="铭牌图片"style="height:auto;width:100%" />}</div> </div> </div> <divclass="weui_cells_title"><h3>其他图片</h3></div> <divclass="weui_cells"> <divclass="weui_cell"> <divid="previewImage2"> <divclass="weui-row">@for (var i = 0; i<ViewBag.otherList.Count; i++)
{
<div class
="weui-col-50"> <imgid='otherPic_@i'src='@ViewBag.otherList[i]'alt="其他图片"style="height:auto;width:100%" /> </div>}</div> </div> </div> </div>

这些代码构建了很多个图片控件,也就是原始的HTML图片控件,如果仅仅这样做,那么只能利用网页的效果,而无法利用微信浏览器预览图片,可以放大缩小的丰富功能。

为了实现我们说需要的功能,需要进行一定的处理,简单的方法,可以设计一个JS函数,然后通过JS函数来实现微信预览图片功能,代码如下所示。

        functionBindClick(selector) {
document.querySelector(selector).onclick
= function(event) {var imgArray =[];var curImageSrc = $(this).attr('src');var oParent = $(this).parent();if (curImageSrc && !oParent.attr('href')) {
$(selector).each(
function(index, el) {var itemSrc = $(this).attr('src');
imgArray.push(itemSrc);
});

wx.previewImage({
current: curImageSrc,
urls: imgArray
});
}
}
}


BindClick(
'#previewImage img');
BindClick(
'#previewImage2 img');

这个函数的做法,是参考网上一个大牛的做法,不过这样做存在一个问题,图片如果有多张的话,那么需要点击第一张图片才能开始预览,不能点击其他几张开始。

为了改进这个缺点,我们可以可以利用Razor的模板实现我们需要的代码生成,如下所示集合了Razor模板生成JS代码,实现了我们所需要JS代码的生成。

        var urls =[];
@foreach (
var url inViewBag.mainList)
{
<text>urls.push('@url');</text> }
@
for (var i = 0; i < ViewBag.mainList.Count; i++)
{
<text>document.querySelector('#mainPic_@i').onclick = function() {
wx.previewImage({
current: $(
this).attr("src"),//urls[@i], urls: urls
});
};
</text> }var urlsOther =[];
@foreach (
var url inViewBag.otherList)
{
<text>urlsOther.push('@url');</text> }
@
for (var i = 0; i < ViewBag.otherList.Count; i++)
{
<text>document.querySelector('#otherPic_@i').onclick = function() {
wx.previewImage({
current: $(
this).attr("src"),//urls[@i], urls: urlsOther
});
};
</text> }

JS代码的生成后的代码如下所示.

        var urls =[];
urls.push(
'http://www.iqidi.com/UploadFiles/设备铭牌图片/TIzMZl04X1iqkHMP44hXFHjQ-yyvrxS-tgwgzMGfwe9AUMTxKohcVC6w6O.jpg');

document.querySelector(
'#mainPic_0').onclick = function() {
wx.previewImage({
current: $(
this).attr("src"),//urls[0], urls: urls
});
};
var urlsOther =[];
urlsOther.push(
'http://www.iqidi.com/UploadFiles/设备铭牌图片/lJk_LNwxTGfL5SNpmJwWyfyBONa2JRO7uzu3PJV3aGsrilPPHY2r-ymU00.jpg');

document.querySelector(
'#otherPic_0').onclick = function() {
wx.previewImage({
current: $(
this).attr("src"),//urls[0], urls: urlsOther
});
};

这样最终就可以实现我们所需要的效果了,当然多张图片也不会有任何的问题。

在我们一些Winform程序中,往往需要具有一些特殊的权限才能操作系统文件,我们可以设置运行程序具有管理员权限或者设置运行程序的目录具有写入的权限,如果是在操作系统里面,我们可以设置运行程序以管理员身份运行,或者设置Users用户组在运行目录中具有写入权限都可以解决问题,不过如果我们想通过C#代码进行自动的处理,那么应该如何实现呢?

1、系统设置管理员权限或者目录写入权限

如果我们需要让程序以管理员身份运行,那么可以通过设置快捷方式的属性或者应用程序的属性为【以管理员身份运行此程序】即可实现,如下所示。

如果我们需要一些写入文件的权限,如我们程序可能需要操作SQLite文件数据库,那么也可以通过设置Users用户组在运行目录中具有写入权限都可以解决问题,否则可能会出现【 attempt to write a readonly database】的错误。

我们设置步骤如下所示:找到SQLite数据库所在的文件夹,单击右键,属性->安全,为Users用户组添加写入权限。

2、使用C#代码实现

上面的步骤可以解决我们实际碰到的权限访问问题,那么我们如果使用C#代码,应该如何实现这些操作呢?

对于第一个以管理员身份运行程序的处理操作,我们是可以通过程序修改配置的方式实现,这样可以避免一些Winform程序运行时刻的权限问题:

1)在通过winform程序执行cmd命令时,某些情况下如果不是以管理员身份运行,则会提示命令无效。

2)或者通过winform程序执行Windows Service 服务时,也需要以管理员身份才能调用Service服务。

3)处理其他需要管理员身份的相关操作。

我们如果是编译Winform程序,只需要几步就可以在让程序在运行的时候获得管理员身份,如下所示在我们Winform的UI项目【属性】【安全性】里面,勾选ClickOne的设置。

然后我们就可以看到在UI项目【Properties】目录里面,生成了一个app.manifest文件。

这个app.manifest文件是自动生成的,我们修改其中的一项设置,然后取消上面勾选ClickOne的设置就可以了。

把其中app.manifest文件的内容:

<requestedExecutionLevel level="
asInvoker
" uiAccess="false" />

改为:

<requestedExecutionLevel level="
requireAdministrator
" uiAccess="false" />

即可,这样取消上面勾选ClickOne的设置,然后重新编译整个程序即可。

在程序运行的时候,会提示“用户账户控制”来获取管理员权限运行,点击“是”则获取了管理员权限。

对于需要为指定目录设置用户组权限,那么也是可以通过C#代码进行处理的。

一般情况下,我们可以在程序安装或者启动的时候,对目录进行用户组权限的处理,这样程序运行起来就自然具有对应目录的读写权限了。

如我们在程序启动的时候处理,那么我们可以在Main函数的里面进行设置。

        /// <summary>
        ///应用程序的主入口点。/// </summary>
[STAThread]private static voidMain()
{

}

为了方便处理,我们添加一个公共的函数,用来处理用户组的目录权限访问操作,C#代码如下所示。

        /// <summary>
        ///为指定用户组,授权目录指定完全访问权限/// </summary>
        /// <param name="user">用户组,如Users</param>
        /// <param name="folder">实际的目录</param>
        /// <returns></returns>
        private static bool SetAccess(string user, stringfolder)
{
//定义为完全控制的权限 const FileSystemRights Rights =FileSystemRights.FullControl;//添加访问规则到实际目录 var AccessRule = newFileSystemAccessRule(user, Rights,
InheritanceFlags.None,
PropagationFlags.NoPropagateInherit,
AccessControlType.Allow);
var Info = newDirectoryInfo(folder);var Security =Info.GetAccessControl(AccessControlSections.Access);boolResult;
Security.ModifyAccessRule(AccessControlModification.Set, AccessRule,
outResult);if (!Result) return false;//总是允许再目录上进行对象继承 const InheritanceFlags iFlags = InheritanceFlags.ContainerInherit |InheritanceFlags.ObjectInherit;//为继承关系添加访问规则 AccessRule = newFileSystemAccessRule(user, Rights,
iFlags,
PropagationFlags.InheritOnly,
AccessControlType.Allow);

Security.ModifyAccessRule(AccessControlModification.Add, AccessRule,
outResult);if (!Result) return false;

Info.SetAccessControl(Security);
return true;
}

然后我们在Main函数里面进行调用就可以了。

        /// <summary>
        ///应用程序的主入口点。/// </summary>
[STAThread]private static voidMain()
{
//为用户组指定对应目录的完全访问权限 SetAccess("Users", Application.StartupPath);//界面汉化 System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("zh-Hans");
DevExpress.UserSkins.BonusSkins.Register();
DevExpress.Skins.SkinManager.EnableFormSkins();

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(
false);
Application.ThreadException
+= newSystem.Threading.ThreadExceptionEventHandler(Application_ThreadException);//登录界面 Login dlg = newLogin();
dlg.StartPosition
=FormStartPosition.CenterScreen;if (DialogResult.OK ==dlg.ShowDialog())
{
if(dlg.bLogin)
{
SplashScreen.Splasher.Show(
typeof(SplashScreen.frmSplash));

gc.MainDialog
= newMainForm();
gc.MainDialog.StartPosition
=FormStartPosition.CenterScreen;

Application.Run(gc.MainDialog);
}

}
dlg.Dispose();
}

这样在程序运行后,我们就可以看到对应目录具有完全的读写操作权限了,这样对于一些如读写SQLite出错的问题,也就迎刃而解了。

以上就是我对于两种不同权限访问的处理经验总结,希望给在Winform开发中的同行参考,感谢耐心的阅读和支持。

在最新的MVC4+EasyUI的Web开发框架里面,我整合了关于网购运单处理的一个模块,其中整合了客户导单、运单合并、到货扫描、扣仓、出仓、查询等各个模块的操作,里面涉及到一些运单套打的操作,不过由于之前介绍LODOP不兼容Chrome等浏览器,因此曾经想放弃这个控件的打印处理,不过他们及时推出了“云打印控件C-Lodop“,而且对之前的接口几乎完全兼容,因此在框架里也继续沿用了这个控件来进行相关的打印处理,包括常规的打印和运单信息套打等处理。

1、控件的安装

这个云控件C-Lodop(
http://www.lodop.net/
)其实是在本地安装后,驻留服务提供JS的服务的,安装后启动程序后界面如下所示。

这种通过服务器提供JS服务的方式,比插件方式好很多,测试过Chrome浏览器也可以顺利打印了,原来的LODOP插件方式的打印是不兼容的。

通过它们本身自带的JS初始化代码,可以了解到该控件目前采用非插件方式进行处理打印操作的了。

//让其它电脑的浏览器通过本机打印(适用例子):
var oscript = document.createElement("script");
oscript.src
= "/CLodopfuncs.js";var head = document.head || document.getElementsByTagName("head")[0] ||document.documentElement;
head.insertBefore(oscript, head.firstChild);
//让本机浏览器打印(更优先): oscript = document.createElement("script");
oscript.src
= "http://localhost:8000/CLodopfuncs.js?priority=1";var head = document.head || document.getElementsByTagName("head")[0] ||document.documentElement;
head.insertBefore(oscript, head.firstChild);
//本机浏览器的后补端口8001(这种兼顾做法可能报错不用理它): oscript = document.createElement("script");
oscript.src
= "http://localhost:8001/CLodopfuncs.js?priority=2";var head = document.head || document.getElementsByTagName("head")[0] ||document.documentElement;
head.insertBefore(oscript, head.firstChild);

官方例子提供了LodopFuncs.js文件用来构建打印控件的,其中在LodopFuncs.js文件里面定义了getLodop函数,用来获得打印控件对象的。

检查是否安装了云打印控件的JS代码如下所示。

<script language="javascript" type="text/javascript">    
    functionCheckIsInstall() {try{var LODOP=getLodop();if(LODOP.VERSION) {if(LODOP.CVERSION)
alert(
"当前有C-Lodop云打印可用!\n C-Lodop版本:"+LODOP.CVERSION+"(内含Lodop"+LODOP.VERSION+")");elsealert("本机已成功安装了Lodop控件!\n 版本号:"+LODOP.VERSION);

};
}
catch(err){
}
};
</script>

2、云打印控件C-Lodop的使用

这个控件和原来的LODOP的使用保持一致性,不用修改原来的代码就可以直接使用最新的打印方式,非常赞,关于这个控件的使用,我在前面介绍了很多相关的使用过程。

如在Winform里面利用网页套打证件的案例《
使用NVelocity生成内容的几种方式
》。

以及在Web页面的套打处理《
Web打印的解决方案之证件套打
》,以及《
Web打印的解决方案之普通报表打印
》,里面对控件的使用操作做了很详细的介绍。

该控件提供了很详细的各种案例(
http://www.lodop.net/demo.html
),可以参考学习使用。

在前面介绍了,我在框架里面的网购运单处理里面,继续使用了这个控件进行套打的处理,例如我们需要套打类似这样的界面内容。

我们设计好套打页面内容,如下所示。

设计好的内容,我们可以把它们转换为页面里面的JS代码如下所示。

    <scriptsrc="/Content/JQueryTools/LODOP/CheckActivX.js"></script>
    <scripttype="text/javascript">
        varLODOP;//声明为全局变量

        functionPreview() {//打印预览
LODOP=getLodop();
LODOP.PRINT_INITA(
-1,-1,824,1129,"运单套打");

CreateLicenseData();
LODOP.SET_PREVIEW_WINDOW(
2,0,0,800,600,"");
LODOP.SET_PRINT_PAGESIZE(
1,0,0,"A4");
LODOP.PREVIEW();
};
functionSetup() {//打印维护 LODOP=getLodop();
LODOP.PRINT_INITA(
-1,-1,824,1129,"运单套打");

CreateLicenseData();
LODOP.PRINT_SETUP();
};
functionDesign() {//打印设计 LODOP=getLodop();
LODOP.PRINT_INITA(
-1,-1,824,1129,"运单套打");

CreateLicenseData();
LODOP.PRINT_DESIGN();
};
functionCreateLicenseData() {if(printID!=undefined&&printID!= '') {//使用同步方式,使得联动的控件正常显示 $.ajaxSettings.async= false;//首先用户发送一个异步请求去后台实现方法 $.getJSON("/BillDetail/FindByID?id=" +printID,function(info) {

LODOP.ADD_PRINT_SETUP_BKIMG(
"<img src='/Content/Template/空白套打模板.png'/>");
LODOP.SET_SHOW_MODE(
"BKIMG_IN_PREVIEW",true);//预览包含背景 LODOP.SET_SHOW_MODE("BKIMG_PRINT",true);//打印内容包含背景 LODOP.ADD_PRINT_BARCODE(78,441,262,56,"128C", info.FenyunDanhao);
LODOP.ADD_PRINT_BARCODE(
684,441,262,56,"128C", info.FenyunDanhao);

LODOP.ADD_PRINT_TEXT(
186,287,277,39, info.Shou_Com);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
236,378,186,28, info.Shou_Name);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
276,283,284,67, info.Shou_Dizhi);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
362,306,254,30, info.Shou_Phone);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
404,319,51,30, info.Jianshu);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
400,476,95,31, info.Zhongliang);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
187,33,227,39, info.Fa_Gongsi);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
277,33,230,65, info.Fa_Dizhi);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
364,37,227,25, info.Fa_Phone);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
410,37,226,26, info.Pinming);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
803,34,234,39, info.Fa_Gongsi);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
895,32,238,66, info.Fa_Dizhi);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
979,33,234,25, info.Fa_Phone);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
1024,35,228,25, info.Pinming);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
802,285,287,39, info.Shou_Com);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
849,373,198,28, info.Shou_Name);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
894,285,287,67, info.Shou_Dizhi);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
980,287,281,26, info.Shou_Phone);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
1021,317,59,29, info.Jianshu);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
LODOP.ADD_PRINT_TEXT(
1017,477,95,30, info.Zhongliang);
LODOP.SET_PRINT_STYLEA(
0,"FontSize",12);
});
}
}
//打印预览 varprintID;functionPrintDetail() {varrow=$("#gridDetail").datagrid("getSelected");if(row) {varindex=$('#gridDetail').datagrid('getRowIndex', row);
printID
=row.ID;
Preview();
}
else{
$.messager.alert(
"提示","请选择一个记录");
}
}
//批量打印 functionBatchPrint() {//得到用户选择的数据的ID varrows=$("#gridDetail").datagrid("getSelections");if(rows.length>= 1) {//然后确认发送异步请求的信息到后台删除数据 $.messager.confirm("批量打印确认","您确认批量打印选定的记录吗?",function(action) {if(action) {for(vari= 0; i<rows.length; i++) {
LODOP
=getLodop();
LODOP.PRINT_INIT(
"");
LODOP.SET_PRINT_PAGESIZE(
1,0,0,"A4");

printID
=rows[i].ID;
CreateLicenseData();

LODOP.PRINT();
}
}
});
}
else{
$.messager.alert(
"提示","请选择你要批量打印的数据");
}
}
//打印维护 functionPrintSetup() {varrow=$("#gridDetail").datagrid("getSelected");if(row) {varindex=$('#gridDetail').datagrid('getRowIndex', row);
printID
=row.ID;
Setup();
}
}
</script>

其中这个打印界面中,还用到了二维码的打印处理操作,这样很方便直接使用条码枪直接读取,如下代码:

LODOP.ADD_PRINT_BARCODE(78, 441, 262, 56, "128C", info.FenyunDanhao);
LODOP.ADD_PRINT_BARCODE(684, 441, 262, 56, "128C", info.FenyunDanhao);

然后在主页面里面设定打印的操作功能按钮进行处理,下面是我们基于EasyUI的Web框架界面效果。

打印预览界面如下所示,实际打印的时候,我们可以设定不打印背景图片。

如果是常规打印,那么他们的界面和我们看到的页面内容非常接近,如下所示。

参考代码如下所示。

@*添加对LODOP控件的支持*@<scripttype="text/javascript">
        varLODOP;//声明为全局变量

        functionPreview() {//正本打印
CreateLicenseData();
LODOP.SET_SHOW_MODE(
"PREVIEW_IN_BROWSE",1);
LODOP.PREVIEW();
};
functionPrintA() {
CreateLicenseData();
LODOP.PRINTA();
};
functionSetup() {//正本打印维护 CreateLicenseData();
LODOP.PRINT_SETUP();
};
functionDesign() {//正本打印设计 CreateLicenseData();
LODOP.PRINT_DESIGN();
};
functionCreateLicenseData() {
LODOP
=getLodop();

LODOP.PRINT_INIT(
"政策法规");varstrBodyStyle= "<link type='text/css' rel='stylesheet' href='/Content/Themes/Default/style.css' /><style><!--table { border:1;background-color: #CBCBCC } td {background-color:#FFFFFE;border: 1; } th { background-color:#F1F1F3;padding-left:5px;border:1}--></style>";varstrFormHtml=strBodyStyle+ "<body>" +document.getElementById("printContent").innerHTML+ "</body>";
LODOP.ADD_PRINT_HTM(
20,40,610,900, strFormHtml);
LODOP.PREVIEW();
}
functionSaveAs() {varid=$('#ID2').val();
window.open(
'/Information/ExportWordById?id=' +id );
}
</script>

以上就是基于新版云打印控件C-Lodop的使用效果和代码,希望对大家有所帮助。

在常规的后台管理系统或者前端界面中,一般都有一个导航菜单提供给用户,方便选择所需的内容。基于Metronic的Bootstrap开发框架,是整合了Metroinc样式,以及Boostrap组件模块的内容,因此菜单的效果自然也是和Bootstrap一脉相承的。基于经常使用的几种菜单样式,本文进行了相关的介绍和展示。

1、菜单展示的三种方式

菜单的展示可以做成各种各样的效果,本文只是针对性的介绍其中的三种,两种左侧竖放的菜单,一种是水平横放的菜单。

样式1:

菜单收缩后界面如下所示。

样式2:

样式3:

水平样式的菜单如下所示:

2、几种布局的组织方式

由于这几种布局方式,它们的框架布局页面(也就是Layout页面)内容有所不同,一般情况下我们可以把它分别定义为一种Layout页面,这样在实际使用的时候包含它即可。

我根据上面几种样式,分别定义了几个不同的Layout页面,如下所示

一般情况下,我们生成一个视图页面,会提示我们选择不同的视图布局页面的,确认之后会在页面顶部应用对应的Layout页面。

如果我们需要动态指定页面的布局内容,可以在上面使用一个变量来处理,如下代码所示。

@{
Layout
= ConfigData.ViewLayoutFile;//"~/Views/Shared/_Layout.cshtml"; ViewBag.Title = "角色信息";
}

实际上,我们一般的页面布局确定好后,就相对比较少变更的,所以为了不打破这个生成页面的布局内容,我们可以把前面处理好的Layout1/2/3等这些复制到Layout页面里面进行处理即可。

不过页面里面还是需要根据不同布局页面,指定不同的资源的。

我们添加样式的资源如下所示。

            //开始全局必需样式引用
            css_metronic.Include("~/Content/metronic/assets/global/plugins/font-awesome/css/font-awesome.min.css","~/Content/metronic/assets/global/plugins/simple-line-icons/simple-line-icons.min.css","~/Content/metronic/assets/global/plugins/bootstrap/css/bootstrap.min.css","~/Content/metronic/assets/global/plugins/bootstrap-switch/css/bootstrap-switch.min.css",

..........................
//主题全局样式 "~/Content/metronic/assets/global/css/components-rounded.css","~/Content/metronic/assets/global/css/plugins.min.css",//主题布局样式 "~/Content/metronic/assets/layouts/" + Layout + "/css/layout.css","~/Content/metronic/assets/layouts/" + Layout + "/css/themes/default.min.css","~/Content/metronic/assets/layouts/" + Layout + "/css/custom.min.css");

上面红色的Layout是我们根据不同布局页面获得的一个变量,从而能够使得不同布局页面的样式和脚本顺利引入的。

    /// <summary>
    ///系统定义的一些常用变量/// </summary>
    public classConfigData
{
/// <summary> ///Web开发框架所属的系统类型定义/// </summary> public const string SystemType = "WareMis";/// <summary> ///配置视图的样式布局/// </summary> public static LayoutType Layout =LayoutType.Layout2;
}
/// <summary> ///布局样式枚举/// </summary> public enumLayoutType
{
Layout,
Layout2,
Layout3
}

3、菜单的动态生成

为了实现菜单动态的生成,我们需要了解这几种方式的菜单结构,然后我们根据它们的特点进行数据结构的生成即可。

第一第二种布局方式的菜单结构数据是一样的,它们的内容如下所示。

                <ulclass="page-sidebar-menu page-header-fixed "data-keep-expanded="false"data-auto-scroll="true"data-slide-speed="200">
                    <liclass="nav-item start"id="1">
                        <ahref="/Home/index?tid=1"class="nav-link nav-toggle">
                            <iclass="icon-home"></i>
                            <spanclass="title">首页</span>
                            <spanclass="selected"></span>
                            <spanclass="arrow open"></span>
                        </a>
                    </li>@Html.Raw(@ViewBag.MenuString)</ul>

我们可以通过后台生成:
@Html.Raw(@ViewBag.MenuString)

然后输出的界面里即可。

我们来看看前两种菜单的结构,简单的结构如下所示

                    <liclass="nav-item start"id="1">
                        <ahref="/Home/index?tid=1"class="nav-link nav-toggle">
                            <iclass="icon-home"></i>
                            <spanclass="title">首页</span>
                            <spanclass="selected"></span>
                            <spanclass="arrow open"></span>
                        </a>
                    </li>

如果有子菜单的,那么两级菜单的结构如下所示。

            <liclass="nav-item "id="3">
                <ahref="javascript:;"class="nav-link nav-toggle">
                    <iclass="icon-rocket"></i>
                    <spanclass="title">客户管理</span>
                    <spanclass="arrow"></span>
                </a>
                <ulclass="sub-menu">
                    <liclass="nav-item "id="31">
                        <ahref="second?tid=3&sid=31">
                            <iclass="glyphicon glyphicon-th-list"></i>
                            <spanclass="title">客户管理</span>
                        </a>
                    </li>
                    <liclass="nav-item  "id="32">
                        <ahref="second?tid=3&sid=32">
                            <iclass="icon-basket"></i>
                            <spanclass="badge badge-roundless badge-danger">new</span>
                            <spanclass="title">客户联系人</span>
                        </a>
                    </li>
                </ul>
            </li>

如果是三级菜单的结构,如下所示:

          <liclass="nav-item active open"id="2">
                <ahref="javascript:;"class="nav-link nav-toggle">
                    <iclass="icon-basket"></i>
                    <spanclass="title">行业动态</span>
                    <spanclass="selected"></span>
                    <spanclass="arrow open"></span>
                </a>
                <ulclass="sub-menu">
                    <liclass="nav-item  active open"id="21">
                        <ahref="javascript:;"class="nav-link nav-toggle">
                            <iclass="icon-home"></i>
                            <spanclass="title">行业动态</span>
                            <spanclass="arrow"></span>
                            <spanclass="selected"></span>
                        </a>
                        <ulclass="sub-menu">
                            <liclass="nav-item  active open"id="211">
                                <ahref="second?tid=2&sid=21&ssid=211">
                                    <iclass="icon-home"></i>
                                    <spanclass="badge badge-danger">4</span>
                                    <spanclass="title">政策法规</span>
                                </a>
                            </li>
                            <liclass="nav-item "id="212">
                                <ahref="second?tid=2&sid=21&ssid=212">
                                    <iclass="icon-basket"></i>
                                    <spanclass="badge badge-warning">4</span>
                                    <spanclass="title">通知公告</span>
                                </a>
                            </li>
                        </ul>
                    </li>
                </ul>
            </li>

根据这些内容,我们在后台动态输出菜单结构即可。

例如我们在基类控制器里面,通过判断视图的处理,实现菜单内容的输出。

        /// <summary>
        ///重载视图展示界面,方便放置一些常规的ViewBag变量。///如果放在OnActionExecuting,则会导致任何请求都会执行一次,从而导致多次执行,降低响应效率/// </summary>
        protected override ViewResult View(string viewName, string masterName, objectmodel)
{
//登录信息统一设置 if (CurrentUser != null)
{
ViewBag.FullName
=CurrentUser.FullName;
ViewBag.Name
=CurrentUser.Name;//ViewBag.MenuString = GetMenuString(); ViewBag.MenuString = GetMenuStringCache(); //使用缓存,隔一段时间更新 }return base.View(viewName, masterName, model);
}

为了提高菜单的响应处理,避免多次递归耗时,我们可以把生成好的菜单内容,放到缓存里面,通过键值实现处理。

        /// <summary>
        ///使用分布式缓存实现菜单数据的缓存处理/// </summary>
        /// <returns></returns>
        public stringGetMenuStringCache()
{
string itemValue = MemoryCacheHelper.GetCacheItem<string>("GetMenuStringCache_" + CurrentUser.ID , delegate()
{
returnGetMenuString();
},
null, DateTime.Now.AddMinutes(5) //5分钟以后过期,重新获取 );returnitemValue;
}

而对于水平方向的菜单处理,和前面两者有所不同。

如二级菜单数据展示如下所示。

                        <liclass="menu-dropdown classic-menu-dropdown ">
                            <ahref="index.html"class="nav-link  ">
                                <iclass="icon-bar-chart"></i>Default<spanclass="arrow"></span>
                            </a>
                        </li>
                        <liclass="menu-dropdown classic-menu-dropdown active">
                            <ahref="javascript:;"class="nav-link nav-toggle ">
                                <iclass="icon-basket"></i>Dashboard<spanclass="arrow"></span>
                            </a>
                            <ulclass="dropdown-menu pull-left">
                                <liclass=" active">
                                    <ahref="index.html"class="nav-link  active">
                                        <iclass="icon-bar-chart"></i>Dashboard<spanclass="arrow"></span>
                                    </a>
                                </li>
                            </ul>
                        </li>

三级菜单展示的数据如下所示。

                        <liclass="menu-dropdown classic-menu-dropdown ">
                            <ahref="javascript:;"class="nav-link nav-toggle ">
                                <iclass="icon-briefcase"></i>Pages<spanclass="arrow"></span>
                            </a>
                            <ulclass="dropdown-menu pull-left">
                                <liclass="dropdown-submenu ">
                                    <ahref="javascript:;"class="nav-link nav-toggle ">
                                        <iclass="icon-basket"></i>eCommerce<spanclass="arrow"></span>
                                    </a>
                                    <ulclass="dropdown-menu">
                                        <liclass=" ">
                                            <ahref="ecommerce_index.html"class="nav-link ">
                                                <iclass="icon-home"></i>Dashboard</a>
                                        </li>
                                        <liclass=" ">
                                            <ahref="ecommerce_orders.html"class="nav-link ">
                                                <iclass="icon-basket"></i>Orders</a>
                                        </li>
                                    </ul>
                                </li>

                                <liclass="dropdown-submenu">
                                    <ahref="javascript:;"class="nav-link nav-toggle">
                                        <iclass="icon-notebook"></i>Login<spanclass="arrow"></span>
                                    </a>
                                    <ulclass="dropdown-menu pull-left">
                                        <liclass="">
                                            <ahref="page_user_login_1.html"class="nav-link "target="_blank"> Login Page 1 </a>
                                        </li>
                                        <liclass="">
                                            <ahref="page_user_login_2.html"class="nav-link "target="_blank"> Login Page 2 </a>
                                        </li>
                                    </ul>
                                </li>
                            </ul>
                        </li>

我们所需要做的就是把我们数据库里面的菜单数据,动态构建这种递进式的数据结构即可。

如果感兴趣Bootstrap开发框架系列,可以参考学习下面的文章,感谢您的阅读。

基于Metronic的Bootstrap开发框架经验总结(10)--优化Bootstrap图标管理

在MVC控制器里面使用dynamic和ExpandoObject,实现数据转义的输出