2023年2月

在我自己的《Web开发框架》中,用了很多年的EasyUI,最新版本EasyUI为1.4.5,随着版本的更新,其很多功能得到了很大的完善和提高,同时也扩展了一些新的功能,以前在布局和对话框弹出层的自动适应大小的问题,也在最近的一些版本得到了解决,本文在迁移到最新EasyUI版本的时候,总结了一些经验,希望对大家使用这个强大的Web界面组件有所帮助。

1、Web主界面的布局调整

上面的布局是顶部内容+一级菜单、左边菜单,右边主内容为页面内容,页面内容是变化的内容,其他部分为不变的,这样的布局代码如下所示。

<!DOCTYPE html>
<html>
<bodystyle="overflow-y:hidden;"scroll="no">
    <divclass="easyui-layout"data-options="fit:true,border:false">
            <!--顶部Banner-->
            <divregion="north"id="header">
            
            </div>

            <!--左侧导航菜单-->
            <divregion="west"split="true"title="导航菜单"style="width:200px;padding:1px;overflow:hidden;">
            
            </div>

            <!--主工作区-->
            <divid="mainPanle"region="center"title=""style="overflow:hidden;">
            
            </div>

            <!--底部版权标识-->
            <divdata-options="region:'south',split:true"style="height: 40px;background: #D2E0F2;">
            
            </div>
            
    </div>
</body>

</html>

在上面的主页面布局代码里面,<div class="easyui-layout" 外面注意不要包含有Form的标志,否则会出现一些莫名其妙的错误。我们通过
data-options="fit:true,border:false"
来设定布局的自动适应,这样在放大缩小页面的时候,布局总是能够自动适应页面的变化的。

2、DataGrid表格的自动调整处理

在之前的一些版本里面,DataGrid总是没有能够自动实现宽度的自动调整,为了实现这种效果,还需要添加一些JS代码进行处理,这种方式在现在DataGrid支持宽度百分比的属性后,变得简单容易了。

表格的HTML代码如下所示。

            <!-------------------------------详细信息展示表格----------------------------------->
            <tableid="grid"title="用户操作"data-options="iconCls:'icon-view'"></table>

而其中我们自动通过JQuery赋值的JS代码如下所示。

        //实现对DataGird控件的绑定操作
        functionInitGrid(queryData) {
$(
'#grid').datagrid({ //定位到Table标签,Table标签的ID是grid url: '/User/FindWithPager', //指向后台的Action来获取当前用户的信息的Json格式的数据 title: '系统用户信息',
iconCls:
'icon-view',
height:
650,
width:
'100%',
nowrap:
true,
autoRowHeight:
true,
striped:
true,
collapsible:
true,
pagination:
true,
pageSize:
50,
pageList: [
50, 100, 200],
rownumbers:
true,//sortName: 'ID', //根据某个字段给easyUI排序 sortOrder: 'asc',
remoteSort:
false,//idField: 'ID', //不设置idField,翻页不会记录选择 queryParams: queryData, //异步查询的参数 columns: [[
{ field:
'ck', checkbox: true }, //选择 { title: 'ID', field: 'ID', width: 80, sortable: true},
{ title:
'用户编码', field: 'HandNo', width: 80, sortable: true},
{ title:
'用户名/登录名', field: 'Name', width: 120, sortable: true},
{ title:
'真实姓名', field: 'FullName', width: 80, sortable: true},
{
title:
'是否过期', field: 'IsExpire', width: 80, formatter: function(val, rowdata, index) {if(val) {return '<a class="grid_unvisible" href="javascript:void(0)" >' + val + '</a>';
}
else{return '<a class="grid_visible" href="javascript:void(0)" >' + val + '</a>';
}
}
},
....................................
]],
onLoadSuccess:
function() {
$(
".grid_visible").linkbutton({ text: '可用', plain: true, iconCls: 'icon-ok'});
$(
".grid_unvisible").linkbutton({ text: '过期', plain: true, iconCls: 'icon-stop'});

$(
".grid_normal").linkbutton({ text: '正常', plain: true, iconCls: 'icon-ok'});
$(
".grid_deleted").linkbutton({ text: '已删除', plain: true, iconCls: 'icon-stop'});
},

toolbar: [{
id:
'btnAdd',
text:
'添加',
iconCls:
'icon-add',
handler:
function() {
ShowAddDialog();
//实现添加记录的页面 }
},
'-', {
id:
'btnEdit',
text:
'修改',
iconCls:
'icon-edit',
handler:
function() {
ShowEditOrViewDialog();
//实现修改记录的方法 }
},
'-', {
id:
'btnDelete',
text:
'删除',
iconCls:
'icon-remove',
handler:
function() {
Delete();
//实现直接删除数据的方法 }
},
'-', {
id:
'btnView',
text:
'查看',
iconCls:
'icon-table',
handler:
function() {
ShowEditOrViewDialog(
"view");//实现查看记录详细信息的方法 }
},
'-', {
id:
'btnReload',
text:
'刷新',
iconCls:
'icon-reload',
handler:
function() {//实现刷新栏目中的数据 $("#grid").datagrid("reload");
}
}],
onDblClickRow:
function(rowIndex, rowData) {
$(
'#grid').datagrid('uncheckAll');
$(
'#grid').datagrid('checkRow', rowIndex);
ShowEditOrViewDialog();
}
});
};

为了实现其自动伸缩,我们只需要设置
width:
'100%'
就可以了,不需要像以前那样需要自动宽度。

3、对话框的自动调整处理

在我们《Web开发框架》里面,利用DIV层作为界面的子窗口是一种很常见的处理方式,因此弹出的对话框层需要设定好其对应的自动缩放效果。

特别是由于不同的电脑设备上,笔记本和台式电脑的高度不同,还有分辨率不同,因此它们的宽度高度需要非常灵活的自动调整处理,实现我们界面的一致性。

以上界面我们通过样式的调整就可以实现对话框大小的变化,以及跟随整体布局的调整,总体居中的效果了。

    <!--------------------------添加信息的弹出层---------------------------->
    <divid="DivAdd"class="easyui-dialog"style="width:98%;max-width:824px;height:98%;max-height:300px;"closed="true"resizable="true"modal="true"data-options="iconCls: 'icon-add',buttons: '#dlg-buttons', onResize:function(){$(this).dialog('center');}">

   </div>

其中
style="width:98%;max-width:824px;height:98%;max-height:300px;"
确保了对话框不会太大变得难看,我们让它保持一个较好的大小。

其中
onResize:function(){$(this).dialog('center');}
让它在布局变化的时候自动把对话框居中,这样效果更加好看。

以上就是我们通常在实现布局和对话框自动适应大小所做的一些处理,能够很好的适应我们不同的设备分辨率。

使用博客园写博客也有10年有余了,对博客园是有一种莫名的亲切感和深刻的感情的,现在则是从博客园迁移到简书平台。这10多年来一直坚持写着博客,也是对自己的一个很好的技术历程总结。这十多年来,每次学习了一些新的技术,或者有一些感兴趣的方向,都会通过随笔进行记录,有时候也会总结很多自己的开发成果,随着技术路线的成熟,基本上是分享我的Winform界面技术、模块设计思路、代码生成工具、分页控件、公用类库、Visio的C#二次开发、Winfrom/Web/微信方面框架开发,以及各种各样的技术总结。

1、博客迁移到简书的背景历程

以前博客园还在发展期,技术话题可以无所不谈,没有任何避讳,可以介绍下自己的研究所得,也可以介绍自己的技术产品;不过这几年博客园不喜欢任何掺杂产品宣传、广告的文章,即使是比较有技术分享的文章也是禁止一些宣传的,因此写博客很长一段时间,总是小心翼翼把自己软件界面的公司名称、主界面涂改再放上去,搞得很是麻烦。偶尔触犯就被撤下主页,曾经有一次被取消首页发布几个月,这次又来,不让继续发布,因此不太喜欢这种粗暴的方式。

不过回头想想,不让发布首页也无妨,自己可以没有任何内容负担,可以按自己喜欢的方式总结技术内容,即使带上自己的一些产品信息也无妨。同时,还是决定找一个相对比较宽松的博客环境,看过简书、51CTO等一些博客平台,简书给我的印象非常好,从手机阅读书写,以及内容的管理方面,电脑端的处理方式也是非常不错的体验,特别是一些分享也可以做到实时处理,可以在手机自己找到自己的文章发给我自己的客户或者朋友,非常方便。

博客园相对来说,是比较简陋而且移动支持不够的一个博客社区,不过好在主要专注的方向还是.NET,简书从写文章以及管理的角度来感受,是一种完全不同的感觉,你会被感觉作者受到非常特别的优待,而且使用起来也是非常方便,特别是使用Markdow的书写方式,逐渐成为我博客的书写习惯了。

这次决定从博客园迁移到简书来说,也是一个比较艰难的决定,一方面在博客园写了十余年的博客,随笔文章有400多篇,同时也积攒了很多热心的粉丝和朋友,迁移意味着以后的重点在简书里面,同时也希望把随笔文章迁移到简书上,毕竟自己这么多年随笔,也是一笔非常可观的财富和人生经历,每篇随笔少则一两个小时,多则是一两周的研究成果总结而出的随笔。随笔记录着我技术的闪光点,也记录着在研究过程的心路历程,每每看着自己几年前的随笔,心情澎湃,有一种故地重游的熟悉感。因此,自己写博客,也尽可能把一些重要的、特别的、闪光着思考思想的内容写出来,寄希望读者和我共鸣,也希望自己每每返回看自己的随笔,有一种幡然醒悟的感觉。

2、简书及其使用总结

简书使用中文拼音作为其域名(
http://www.jianshu.com/
),简书非常方便的特点是可以随时随地用手机维护自己的博客或者查看其它博客文章,如我的简书博客地址如下(
http://www.jianshu.com/users/5c9663046ca3
)。

移动端的页面效果如下所示:

而其电脑页面效果如下所示。

无论在电脑端,还是移动端,简书的整体界面使用起来是非常舒服的,这也是我最终选择简书作为我博客迁移的归宿的重要原因。

从博客园到简书的迁移过程中,花了一周多的时间完成了100多篇随笔的迁移工作,后面我会逐渐把我自己写的大量博客迁移过来,并在今后把简书作为优先采用的博客平台。

我使用Chrome浏览器作为处理的浏览器,因此基本上把内容复制过去,图片也会自动上传的,不过如果是代码,则需要使用Markdown格式进行处理一下,其他标题也是如此,下面我会介绍一下,常见使用的Markdown语法即可。

3、常用的Markdown语法介绍

如果是用惯了富文本的模式,特别是搞开发的,我们需要粘贴代码,可能有点不太习惯在简书里面编辑,不过我们可以采用简书里面的Markdown编辑器进行编辑,这种是非常方便、强大的格式编辑语法,在简书里面我推荐使用。

一般来说,如果我们进行博客迁移,图片会重新上传到简书的空间(这点很好,防止网站盗链保护不显示),连接会自动处理,如果是本地要上传图片,可以打开图片复制后在浏览器粘贴,图片会自动上传并添加连接的,不过如果是表格这需要另外处理(比较讨厌没有自动处理,我一般为了方便直接截图上传)。

Markdown 是一种用来写作的轻量级
「标记语言」
,它用简洁的语法代替排版,而不像一般我们用的字处理软件
Word

Pages
有大量的排版、字体设置。它使我们专心于码字,用「标记」语法,

下面几种是我们编辑随笔的时候,经常用到的Markdown语法。

标题

这是最为常用的格式,在平时常用的的文本编辑器中大多是这样实现的:输入文本、选中文本、设置标题格式。

而在 Markdown 中,你只需要在文本前面加上
#
即可,同理、你还可以增加二级标题、三级标题、四级标题、五级标题和六级标题,总共六级,只需要增加
#
即可,标题字号相应降低。例如:

# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题

注: # 和「一级标题」之间建议保留一个字符的空格,这是最标准的 Markdown 写法。

列表

列表格式也很常用,在 Markdown 中,你只需要在文字前面加上
-
就可以了,例如:

- 文本1
- 文本2
- 文本3

如果你希望有序列表,也可以在文字前面加上
1.
2.
3.
就可以了,例如:

1. 文本1
2. 文本2
3. 文本3

注: - 1. 和文本之间要保留一个字符的空格。

链接和图片

在 Markdown 中,插入链接不需要其他按钮,你只需要使用
[显示文本](链接地址)
这样的语法即可,例如:

[简书](http://jianshu.io)

在 Markdown 中,插入图片不需要其他按钮,你只需要使用
![](图片链接地址)
这样的语法即可,例如:

![](http://ww4.sinaimg.cn/bmiddle/aa397b7fjw1dzplsgpdw5j.jpg)

注:插入图片的语法和链接的语法很像,只是前面多了一个

引用

在我们写作的时候经常需要引用他人的文字,这个时候引用这个格式就很有必要了,在 Markdown 中,你只需要在你希望引用的文字前面加上
>
就好了,例如:

> 一盏灯, 一片昏黄; 一简书, 一杯淡茶。 守着那一份淡定, 品读属于自己的寂寞。 保持淡定, 才能欣赏到最美丽的风景! 保持淡定, 人生从此不再寂寞。

注: > 和文本之间要保留一个字符的空格。

最终显示的就是:

粗体和斜体

Markdown 的粗体和斜体也非常简单,用两个
*
包含一段文本就是粗体的语法,用一个
*
包含一段文本就是斜体的语法。例如:

 *一盏灯*, 一片昏黄;**一简书**, 一杯淡茶。 守着那一份淡定, 品读属于自己的寂寞。 保持淡定, 才能欣赏到最美丽的风景! 保持淡定, 人生从此不再寂寞。

最终显示的就是下文,其中「一盏灯」是斜体,「一简书」是粗体:

一盏灯
, 一片昏黄;
一简书
, 一杯淡茶。 守着那一份淡定, 品读属于自己的寂寞。 保持淡定, 才能欣赏到最美丽的风景! 保持淡定, 人生从此不再寂寞。

最终显示的就是:

表格

表格是我觉得 Markdown 比较累人的地方,例子如下:

| Tables        | Are           | Cool  |
| ------------- |:-------------:| -----:|
| col 3 is | right-aligned | $1600 |
| col 2 is | centered | $12 |
| zebra stripes | are neat | $1 |

这种语法生成的表格如下:

Tables Are Cool
col 3 is right-aligned $1600
col 2 is centered $12
zebra stripes are neat $1

代码框

如果你是个程序猿,需要在文章里优雅的引用代码框,在 Markdown下实现也非常简单,只需要用三个 ` 把中间的代码包裹起来。图例:

得到的效果如下所示

在前面随笔《
C#开发微信门户及应用(41)--基于微信开放平台的扫码登录处理
》介绍了基于微信开放平台接口实现的微信扫码直接登录的过程。本篇介绍对扫码登录的一些改进和处理,以便更方便应用在实际项目中。

1、扫码登录配置处理

在我前面的随笔扫码登录处理中,介绍了账号登陆和扫码登录并存的情况,有时候我们可能想只是允许用户扫码登录即可,也就是不需要账号登陆,这种情况我们可以通过进行配置实现处理。
例如我在一个基于微信的系统应用里面,通过配置实现了仅仅允许扫码登录的效果。
也就是我们可以根据配置,因此其他登录界面,仅仅显示微信的扫码登录生成的二维码效果,如下所示。


在实现的代码里面,我们主要就是使用JS对界面显示进行控制即可。
一般情况下,我们在开发初期,可以允许账号密码登陆及扫码登录,一旦配置好相关的账号信息,就只需要扫码登录,屏蔽账号密码登陆了。


其中我们需要处理的HTML代码层就是增加一个openLoginDiv用来显示扫码登录的二维码即可。

其中的JS代码处理如下所示,直接从后台读取相关的信息,在JS上构建二维码。

2、用户扫码绑定及取消

在用户能够使用扫码登录前,我们在后台是需要先记录用户的微信标识的,也就是需要在系统上设置一个功能,让用户使用微信扫一下绑定,同时也应该设置一个功能让其解绑,方便对失效的用户进行处理。
在用户列表的二维码连接上,单击可以对用户进行微信扫码绑定,这样用户可以在扫码登录处直接扫码登录,不需要输入账号密码。


一旦成功绑定用户微信,在微信端会定位到一个绑定成功的页面,如下所示。


如果用户已经绑定了其中的一个用户而再去扫码绑定其他用户,那么会提示用户避免无法重复绑定账号即可。

另外,如果用户已经进行了二维码绑定,则可以在管理界面进行取消绑定,这样可以解绑用户账号和微信之间的关联。


绑定和取消绑定微信扫码操作的JS代码就是通过JQuery实现接口调用接口。

取消绑定微信扫码的界面的处理代码很简单,只需要在后台清楚对应的用户记录的openid和unionid即可。
具体的MVC控制器或者API代码如下所示。

而在底层BLL的代码里面,我们只需要做的就是更新指定的两个字段即可,把它们清空的代码如下所示。

在一个系统里面,往往有很多菜单项目,每个菜单项对应一个页面,一般用户只需要用到一些常用的功能,如果每次都需要去各个层次的菜单里面去找对应的功能,那确实有点繁琐。特别是在菜单繁多,而客户又对系统整体不熟悉的情况下,如果有一个类似浏览器的收藏夹模块,把一些常用的菜单连接保存起来,每次从这个收藏夹主页去找对应的页面,那样确实是省事省力,非常方便。本篇随笔就是介绍在基于Metronic的Bootstrap开发框架里面实现这个收藏夹的思路。

1、系统的收藏夹界面处理效果

为了实现这个收藏夹功能,我们也需要在系统页面的明显位置处放置一个收藏夹模块的入口,以及可以为每个页面添加到对应收藏夹的功能。
经过对比,我们把这些入口功能放在页面标题的附近,这样方便进行快速进行收藏夹,如下效果所示。


当我们在页面上单击【添加到收藏夹】按钮,我们就把对应的页面标题和连接加入到收藏夹记录里面了。

在【查看收藏夹】功能里面,我们可以展示我们加入的页面链接,单击其中某个记录,可以快速进入对应的页面,这样就实现了我们快速进入功能模块的需求了。


这里面最为关键的就是对收藏夹记录的排序处理,向上或者向下移动记录,使之能够符合界面的处理。

2、系统收藏夹的实现过程

了解了上面关于系统页面的收藏夹功能界面效果后,我们需要了解它的具体实现过程,首先我们需要设计一个表用来存储收藏夹对应的信息,页面标题、页面地址、排序等信息。
数据库设计界面如下所示。

我们注意到排序记录用Decimal格式进行存储,我们通过一个有经度的数值进行排序,这样我们可以调整的时候,修改它们之间的大小就可以了。
使用代码生成工具Database2Sharp快速生成底层的相关代码和Web的控制器和视图代码,然后整合到框架里面,这样我们就可以具有整个模块的界面和处理代码了。

由于一般情况下,我们对数据的显示编辑界面是相对标准的,对于收藏夹的入口展示的需求不太一样,我们需要参考列表界面增加一个视图,用来展示简单的入口界面,如图介绍所示。


这个界面里面包含了对记录的移动处理,包括向上或者向下。
前面介绍了,我们对记录的排序主要通过decimal类型的Seq字段实现的。
我们在实体类初始化的时候,给排序的赋值为当前时间的Unix时间戳。

其中上面的DateTimeToInt函数代码如下所示,也是我们常用的处理方式。

        /// <summary>
        ///扩展时间接口,可以返回整形数值/// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public static int DateTimeToInt(thisDateTime time)
{
System.DateTime startTime
= TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));return (int)(time -startTime).TotalSeconds;
}

为了实现记录的移动,我们需要在业务BLL层实现一个移动的逻辑处理,方便在控制器里面调用。

        /// <summary>
        ///更新向上或者向下的顺序/// </summary>
        /// <param name="id">记录的ID</param>
        /// <param name="moveUp">往上,还是往下移动,往上则为true</param>
        /// <returns></returns>
        public bool UpDown(string id, bool moveUp)

实现的函数代码如下所示

/// <summary>
///更新向上或者向下的顺序/// </summary>
/// <param name="id">记录的ID</param>
/// <param name="moveUp">往上,还是往下移动,往上则为true</param>
/// <returns></returns>
public bool UpDown(string id, boolmoveUp)
{
//设置排序的规则 bool IsDescending = true;bool result = false;
WebFavoriteInfo info
=FindByID(id);if (info != null)
{
//构建查询的条件 string condition = "";if(IsDescending)
{
condition
= string.Format("Seq {0} {1}", moveUp ? ">" : "<", info.Seq);
}
else{
condition
= string.Format("Seq {0} {1}", moveUp ? "<" : ">", info.Seq);
}
var list =baseDal.Find(condition);decimal newSeq =0M;switch(list.Count)
{
case 0:
newSeq
= info.Seq;//已在顶部或者底部,顺序默认不变 break;case 1://上面或者下面有一个记录 if(IsDescending)
{
newSeq
= moveUp ? (list[0].Seq + 1M) : (list[0].Seq -1M);
}
else{
newSeq
= !moveUp ? (list[0].Seq + 1M) : (list[0].Seq -1M);
}
break;case 2://中间区域,取平均值 newSeq = (list[0].Seq + list[1].Seq) /2M;break;default://多于两个的情况 if(moveUp)
{
newSeq
= (list[list.Count - 2].Seq + list[list.Count - 1].Seq) /2M;
}
else{
newSeq
= (list[0].Seq + list[1].Seq) /2M;
}
break;
}
//统一修改顺序 info.Seq =newSeq;
result
=Update(info, info.ID);
}
returnresult;
}

这样我们在MVC的控制器里面,对这个BLL层接口进行进一步封装,方便页面前端进行Ajax调用处理即可,封装代码如下所示。

/// <summary>
///移动记录/// </summary>
/// <param name="id">记录ID</param>
/// <param name="up">向上为true,否则为false</param>
/// <returns></returns>
[HttpPost]public ActionResult UpDown(string id, boolup)
{
CommonResult result
= newCommonResult();if(!string.IsNullOrEmpty(id))
{
try{
result.Success
= BLLFactory<WebFavorite>.Instance.UpDown(id, up);
}
catch(Exception ex)
{
result.ErrorMessage
=ex.Message;
}
}
returnToJsonContent(result);
}

这样我们在页面前端的界面视图里面,就可以对这个方法进行调用了。
首先在通过JS绑定生成前端HTML代码,如下所示。

$("#grid_body").html("");

$.each(data.rows,
function(i, item) {var tr = "<tr>";
tr
+= "<td><a class='btn btn-sm blue' href='" + item.Url + "'>" + item.Title + "</a></td>";

tr
+= "<td>";
tr
+= "<a href='javascript:;' class='btn btn-sm green' onclick=\"Up('" + item.ID + "')\" title='向上移动'><span class='glyphicon glyphicon-arrow-up icon-state-danger'></span></a>";
tr
+= "<a href='javascript:;' class='btn btn-sm blue' onclick=\"Down('" + item.ID + "')\" title='向下移动'><span class='glyphicon glyphicon-arrow-down'></span></a>";
tr
+= "</td>";

tr
+= "</tr>";
$(
"#grid_body").append(tr);
});

然后通过Up或者Down函数进行处理,向上或者向下移动位置。

var UpDownUrl = "/WebFavorite/UpDown"
functionUp(id) {var postData = { id: id, up: true};
$.post(UpDownUrl, postData,
function(json) {var data =$.parseJSON(json);if(data.Success) {
showTips(
"向上移动成功");
Refresh();
//刷新页面数据 }else{
showTips(data.ErrorMessage);
}
});
}
functionDown(id) {var postData = { id: id, up: false};
$.post(UpDownUrl, postData,
function(json) {var data =$.parseJSON(json);if(data.Success) {
showTips(
"向下移动成功");
Refresh();
//刷新页面数据 }else{
showTips(data.ErrorMessage);
}
});
}

这样就实现了我们所需要的移动顺序的操作了,另外添加的时候,我们判断对应用户是否有添加URL了,如果存在则不需要重复添加即可,前端只需要通过Ajax调用,然后响应处理即可。
通过这些代码的实现,我们可以实现收藏夹的快速管理和快速入口,为用户的使用提供了更加友好的体验。

在上篇随笔《
基于Metronic的Bootstrap开发框架经验总结(12)--页面链接收藏夹功能的实现
》上,我介绍了链接收藏夹功能的实现,以及对收藏记录的排序处理。该篇随笔主要使用功能按钮的方式移动收藏记录,功能虽然实现的还算不错,不过文章出来后,有读者同行指出可以利用直接拖动的方式实现排序更方便,因此对其中列表记录的排序进行了研究,从而介绍了如何利用Sortable开源JS组件实现拖动排序的处理,本篇随笔介绍了该组件在连接收藏夹排序中的应用。

1、收藏记录的排序处理回顾

上篇随笔介绍的收藏夹处理,主要就是为了方便用户快速进入常用功能的一个模块,随着收藏夹记录的增多,我们有必要对它们进行合理的排序,以方便我们的使用。
原来的收藏夹记录排序界面如下所示。


这个界面里面包含了对记录的移动处理,包括向上或者向下。
实现的逻辑代码主要就是对当前记录的前后记录的排序进行调整的处理,从而实现位置的调整,代码如下所示。

/// <summary>
///更新向上或者向下的顺序/// </summary>
/// <param name="id">记录的ID</param>
/// <param name="moveUp">往上,还是往下移动,往上则为true</param>
/// <returns></returns>
public bool UpDown(string id, boolmoveUp)
{
//设置排序的规则 bool IsDescending = true;bool result = false;
WebFavoriteInfo info
=FindByID(id);if (info != null)
{
//构建查询的条件 string condition = "";if(IsDescending)
{
condition
= string.Format("Seq {0} {1}", moveUp ? ">" : "<", info.Seq);
}
else{
condition
= string.Format("Seq {0} {1}", moveUp ? "<" : ">", info.Seq);
}
var list =baseDal.Find(condition);decimal newSeq =0M;switch(list.Count)
{
case 0:
newSeq
= info.Seq;//已在顶部或者底部,顺序默认不变 break;case 1://上面或者下面有一个记录 if(IsDescending)
{
newSeq
= moveUp ? (list[0].Seq + 1M) : (list[0].Seq -1M);
}
else{
newSeq
= !moveUp ? (list[0].Seq + 1M) : (list[0].Seq -1M);
}
break;case 2://中间区域,取平均值 newSeq = (list[0].Seq + list[1].Seq) /2M;break;default://多于两个的情况 if(moveUp)
{
newSeq
= (list[list.Count - 2].Seq + list[list.Count - 1].Seq) /2M;
}
else{
newSeq
= (list[0].Seq + list[1].Seq) /2M;
}
break;
}
//统一修改顺序 info.Seq =newSeq;
result
=Update(info, info.ID);
}
returnresult;
}

以上的代码,通过判断当前移动记录的位置,然后获取排序在其上面或者下面的记录,如果记录数量为0 ,那么就是顶端或者底端的了,如果是1条记录,那么就是在该记录上增加或者减除某个数值就作为新排序位置的值即可。如果是大于或等于2条记录记录,则取其最近的两个记录,取他们的平均值即可。

2、收藏夹的拖动排序处理

上面的处理能够满足基本的要求,而且调整位置也是正确的。但是我们如果能够拖动列表项进行排序的话,那样就更加方便、更加友好的了。
基于拖动的排序,我寻找到了一个比较好的JS处理组件(
Sortable
)这个在github上排名比较高,估计用的人也很多。
这个控件的使用相对比较简单,代码如下所示。

<ulid="items">
    <li>item 1</li>
    <li>item 2</li>
    <li>item 3</li>
</ul>var el = document.getElementById('items');
var sortable = new Sortable(el);

我们先来看看我最终使用Sortable整合好的界面效果。


这样我们就可以通过移动记录的方式进行调整位置。
列表的展示,我们还是使用分页的方式,为了提高检索效率。

    <divclass="portlet-body flip-scroll">
        <divclass="portlet-body">
            <div>
                <span>每页显示</span>
                <selectid="rows"onchange="ChangeRows()">
                    <option>10</option>
                    <optionselected>50</option>
                    <option>100</option>
                    <option>1000</option>
                </select>
                <span>条记录</span>&nbsp;&nbsp;
                <span>共有记录:</span><spanid='totalCount'class="label label-success">0</span>条,总页数:<spanid='totalPageCount'class="label label-success">0</span>页。</div>
            <hr/>
            <divid="grid_body"class='list-group'></div>

            <divclass="paging-toolbar">
                <ulid='grid_paging'></ul>
            </div>
        </div>
    </div>

在这里面我们通过在grid_body里面构建一系列的列表记录即可。

<divclass="list-group-item">
    <spanclass="glyphicon glyphicon-move"aria-hidden="true"></span>
    <aclass="btn btn-sm blue"id="e1f462c6-c749-4258-836f-e13ee8c8acd7"href="http://localhost:2251/User/Index?tid=2744DBF5-A648-47C1-9E9A-D8B405884389">系统用户信息</a>
    <iclass="js-remove"></i>
</div>

在记录的更新后,该Sortable组件有一个OnUpdate的事件可以处理,如下所示。

var grid_body = document.getElementById('grid_body');newSortable(grid_body, {
handle:
'.glyphicon-move',
filter:
".js-remove",
animation:
150,
onUpdate:
function (/**Event*/evt) {var list = [];//构造集合对象 $('.list-group div a').each(function(i, item) {
list.push({
'Text': item.text, 'Value': item.href });
});
var url = "/WebFavorite/EditFavorite";var postData ={ list: list };
$.post(url, postData,
function(json) {var data =$.parseJSON(json);if(data.Success) {//showTips("操作成功"); Refresh();//刷新页面数据 }else{
showTips(data.ErrorMessage);
}
});
},
});

这样我们把业务处理交给EditFavorite方法了,这里面主要对列表记录进行统一更新即可,处理逻辑就是先删除以前的记录,然后添加列表的集合记录,并且设置它们的排序记录为合适的顺序即可。

/// <summary>
///编辑记录列表/// </summary>
/// <param name="list">记录列表</param>
/// <returns></returns>
[HttpPost]public ActionResult EditFavorite(List<CListItem>list)
{
CommonResult result
= newCommonResult();var userid =CurrentUser.ID;

DbTransaction trans
= BLLFactory<WebFavorite>.Instance.CreateTransaction();if (trans != null)
{
try{//先删除就记录 var condition = string.Format("Creator='{0}'", userid);
BLLFactory
<WebFavorite>.Instance.DeleteByCondition(condition, trans);//逐条添加记录 int i =list.Count;foreach (CListItem item inlist)
{
WebFavoriteInfo info
= newWebFavoriteInfo();
info.Title
=item.Text;
info.Url
=item.Value;
info.Seq
= i--;
info.Creator
=CurrentUser.ID.ToString();

BLLFactory
<WebFavorite>.Instance.Insert(info, trans);
}

trans.Commit();
result.Success
= true;
}
catch(Exception ex)
{
result.ErrorMessage
=ex.Message;
trans.Rollback();
LogHelper.Error(ex);
}
}
returnToJsonContent(result);
}

以上就是对收藏夹列表进行拖动排序的改进处理,希望在实际的项目中能够合理利用这个Sortable的JS组件,能够提高我们用户的体检效果。