文章管理的视图与文件管理区别不大,都是分左右两部分。文章管理的左边是树状的分类列表,右边以Grid形式显示的文章列表。基本上重复劳动比较 多,使用Sencha Architect这个可视化工具来做这方面的设计,是不错的选择,如果公司收益好,强烈建议使用,一个开发包的价格是399美金,大约2800人民币, 还是很划得来的,起码比请多个程序员划得来。
废话又有点多了,转回正题,在Scripts\app\view目录下,创建一个Content目录,用来存放文章管理需要用到的视图,主要的视图包括文章管理的主视图、分类编辑窗口和文章编辑窗口。
接着,在Content目录下创建一个名称为View.js的文件,用来定义主视图,基本结构代码如下:
Ext.define('SimpleCMS.view.Content.View',{
    extend: 'Ext.container.Container',
    alias: 'widget.contentview',
    layout:"border",
 
    initComponent: function () {
        var me = this;
 
        me.callParent(arguments);
    }
 
});
 
接下来就是一步步添加组件了,先完成树的定义,代码如下:
me.tree= Ext.widget("treepanel", {
    title: "文章类别", region: "west", collapsible: true, rootVisible:true, store: "CategoriesTree",
    width: 200, minWidth: 100, split: true
});
 
接着是Grid,代码如下:
me.grid= Ext.widget("grid", {
    title: "文章列表", region: "center",
    store: "Contents",
    selType: "checkboxmodel",
    selModel: { checkOnly: false, mode:"MULTI" },
    columns: [
            { text: '编号', dataIndex: 'ContentId', width: 80},
            { text: '标题', dataIndex: 'Title', flex: 1 },
        {xtype: "datecolumn", text: '创建时间', dataIndex: 'Created', format: "Y-m-d H:i:s", width: 150},
            { text: '排序序数', dataIndex: 'SortOrder', width: 80},
            { text: '点击量', dataIndex: 'Hits', width: 80 },
        { text: '标签', dataIndex: 'Tags',width:150 }
     ]
});
 
Grid使用了复选框作为选择行的方式,也是一向习惯了。其它方面,应该问题不大。
 
最后是将树和Grid放到容器里,代码如下:
me.items = [me.tree, me.grid];
 
现在,可以测试一下效果了,不过,这还有修改控制器,切换到文章管理的控制器,在init方法内创建视图,并添加到面板里,这个应该很熟悉了,在用户管理和图片管理已经做过了。忘记了就直接复制过来修改一下创建的视图就行了,具体代码如下:
var me= this,
    panel = me.getContentPanel(),
    view = Ext.widget("contentview");
panel.add(view);
 
代码的panel用到了getContentPanel方法,返回文章管理的面板,因而要添加引用,代码如下:
refs: [
     { ref: "ContentPanel", selector:"#contentPanel" },
],
 
当然了,不要忘记在views配置项内添加视图了,代码如下:
views:[
    'Content.View'
],
在 浏览器打开后,屏幕居然什么都没显示出来,而在Firebug居然显示两个“Layout run failed”错误,说明布局有问题。这个在之前也碰到过了,就是主面板定义中,没定义文章管理面板的布局,现在切换到主面板的视图,在文章管理的面板定 义上加回布局定义,布局使用Fitlayout。
刷新一下浏览器,就可看到如图40的效果了。
 CMS:文章管理之视图(1) _ MVC
图40 文章管理的界面
现 在来完成分类树的功能,先完成树的显示。在完成前,先要用实体框架,将数据库中的表转换为对象。在解决方案内添加一个新项,然后如图41所示,在添加新项 窗口中,先选择数据模版,然后在中间选择ADO.NET实体数据模型,将名称修改为SimpleCMS.edmx后,单击添加按钮。
 CMS:文章管理之视图(1) _ExTJS_02
图41 添加实体框架
在弹出的如图42所示的实体数据模型向导窗口中,选择从数据库生成,然后单击下一步。
 CMS:文章管理之视图(1) _ASP.NET_03
图42 实体数据模型向导窗口
在 切换到如图43所示的选择您的数据连接窗口中,可以单击新建连接按钮新建一个连接。在不过,默认的ApplicationServices连接是在配置文 件中定义好的连接,可以直接使用,就不必再创建新的了。选上“是,在连接字符串中包括敏感数据”后,单击下一步按钮。当然,如果不喜欢在 Web.Config文件包含用户名和密码这些敏感数据,可以另行处理。
 CMS:文章管理之视图(1) _ASP.NET_04
图43 选择数据连接
在如图44所示的选择数据库对象窗口中,展开树中的表,然后如图那样选择以“T_”开头的4个表格,然后单击完成按钮结束向导。
 CMS:文章管理之视图(1) _CM_05
图44 选择数据库对象
等待生成完成后,会在解决方案中看到如图45所示的数据结构图。
 CMS:文章管理之视图(1) _ASP.NET_06
图45 生成的数据结构图
看到图,一定会疑惑,为什么刚才选择了4个表格,怎么在这里只看到3个对象?这是因为连接表T_Content和T_Tag的关联表在对象中只是一种多对多关系,隐含在对象中了,因而,不需要像数据库那样,显式的表示成一个对象。
这 里还要为T_Category添加一个关联,让父节点可以找到它的子节点。在T_Category 的图中单击鼠标右键,在右键菜单中选择添加>关联,在如图46所示的添加关联对话框中,修改右边的实体为T_Category,接着修改左边的多重 性为“0...1(零或一个)”。接着修改左边的导航属性为Childs,这样,就可通过childs属性访问子节点了。然后将右边的导航属性修改为 Parent,这样,通过Parent属性就可访问到父节点了。把复选框的勾去掉后,单击确定按钮完成添加关联操作。
CMS:文章管理之视图(1) _ MVC_07
图46 添加关联对话框
接着在右边的属性列表中,编辑引用约束,在如图47的引用约束对话框中,选择主体为T_Category,然后在表格中将CategoryId的依赖属性设置为ParentId。
CMS:文章管理之视图(1) _CM_08
图47 引用约束对话框
保存一下解决方案,然后新建一个服务器端的控制器Category,添加必要的引用后,添加一个只读的私有变量dc,用来访问SimpleCMSEntities的实例,代码如下:
private readonly SimpleCMSEntities dc = newSimpleCMSEntities();
 
分类树的Store调用的是List方法,因而将Index方法修改为List方法,并加入相应的权限、返回对象和基本结构代码,代码如下:
[AjaxAuthorize(Roles= "普通用户,系统管理员")]
publicJObject List()
{
    bool success = false;
    string msg = "";
    JArray ja = new JArray();
    int total = 0;
    try
    {
        success = true;
 
    }
    catch (Exception e)
    {
        msg = e.Message;
    }
    returnHelper.MyFunction.WriteJObjectResult(success, total, msg, ja);
}
 
树展开一个节点,都会以node作为参数将该节点的id提交,因而,在提取数据是,首先要做的是先从node提取id,代码如下:
intid=-1;
int.TryParse(Request["node"], out id);
 
因为在定义Store时,根节点的id为-1,因而这里要分两种情况处理搜索结果,代码如下:
IQueryable<T_Category>q = null;
if (id== -1)
{
    q = dc.T_Category.Where(m =>m.Hierarchylevel == 0 & m.State == 0).OrderBy(m=>m.Title);
}
else
{
    q = dc.T_Category.Where(m => m.ParentId== id & m.State == 0).OrderBy(m => m.Title);
}
代码中,当id为-1时,就搜索层数为0的记录,否则,则搜索ParentId的等于id的记录。
这里有个问题,因为记录根据标题进行了排序,因而未分类这个类别就不知道跑到那个位置去了,因而,为了保持为分类在顶部,在查询的时候最好把它排除出去,另外再添加到结果中,代码修改如下:
q =dc.T_Category.Where(m => m.Hierarchylevel == 0 & m.CategoryId!=10000& m.State == 0).OrderBy(m=>m.Title);
 
现在要写数据到ja里面了,因为要多次写相同格式的对象,因而,把写对象独立为一个方法比较好,代码如下:
privateJObject writeNode(int id, string text, int parentId, bool isLeaf)
{
    JObject jo = new JObject
    {
        new JProperty("id",id),
        new JProperty("text",text),
        newJProperty("parentId",parentId),
        new JProperty("leaf",newJValue(isLeaf))
    };
    if (!isLeaf)
    {
        jo.Add(new JProperty("children",new JArray()));
    }
    return jo;
}
 
从代码可以看到,方法带4个参数,前3个参数就是返回节点所需的id、text和parentId的字段。而isLeaf的作用就是判断节点是否有子节点,如果有,就添加一个children属性,这样在客户端就能看到一个加号,可以展开。
现在,先在id等于-1时的查询语句下添加以下两个节点:
ja.Add(writeNode(-99,"全部", -1,true));
ja.Add(writeNode(10000, "未分类", -1, true));
添加全部节点,目的是为了方便查看数据,如果不喜,可不要。未分类这个是必须的。注意两个节点的id。
接着就是在判断语句后用循环输出节点了,代码如下:
foreach(var c in q)
{
    bool leaf = c.Childs.Count() > 0 ? false: true;
    int pid = c.ParentId == null ? -1 :(int)c.ParentId;
    ja.Add(writeNode(c.CategoryId, c.Title,pid, leaf));
}
 
在代码中,使用到了刚才设置的关联,通过统计子节点的个数来判断该节点是否有子节点。
生 成一下解决方案,然后刷新一下页面。喔,根节点没自动打开,而且也没隐藏。先切换到树的Store定义,为根节点添加一个expanded配置项,值为 true,让它自动展开。然后切换到视图定义,将rootVisible配置项设置为false。这些事情在复制粘贴过程中经常会发生。
好了,现在刷新一下页面,就会看到如图48的效果了。
CMS:文章管理之视图(1) _CM_09
图48 文章类别树的显示结果
这样,树的显示就完成了,下文继续。
 源代码下载:http://vdisk.weibo.com/s/hL6up