现在,图片管理就剩上传文件功能没有完成了。这需要用到swfupload,在《CMS之图片管理(1)》中有它的下载地址和加入项目的说明。
使用swfupload最麻烦的地方是要有一个HTML元素让它嵌入加载Flash的HTML代码,而且这个HTML元素必须覆盖住Flash来实现功能,这个有点类似做单按钮的上传按钮。
现在,先为swfupload生成一个HTML元素来实现它的功能。实现方法是在显示图片的视图底部添加一个工具栏,然后将工具栏分成两部分,第一部分显示一个SPAN元素,第二部分显示一个进度条来指示上传进度。
在PicManager.js文件中,找到me.items这句代码,在它上面创建一个进度条,代码如下:
me.progress=Ext.widget("progressbar",{text:"上传进度",flex:1});
因为在swfupload的处理方法中还要直接调用进度条,因而这里将它绑定到progress属性,会方便很多。因为水平工具栏默认是使用HBox布局的,因而在进度条上设置flex为1,就会让它占满整工具栏余下的宽度。
接着在图片文件的面板内添加一个dockedItems配置项,在面板底部放置一个工具栏,并在工具栏上放置一个SPAN元素和进度条,代码如下:
dockedItems: [
{ xtype:"toolbar", dock: "bottom",
items: [
{ xtype: "tbtext", text:"<span></span>", width: 70 },
me.progress
]
}
]
代码中,使用了一个TextItem来显示SPAN元素,宽度为70像素。
现在,要解决的是SPAN的id问题,这也是swfupload的要求,它需要根据该id来获取元素。如果统一一个id,那就会有冲突,因而必须想办法设置成不同的id,这个就需要用Ext的id方法,它看返回一个唯一的id。
在创建进度条的代码下添加以下代码获取一个id:
修改一下SPAN元素的代码,为它加上id,代码如下:
"<span id='" + me.spanid +"'></span>"
现在要做的是监听扩展的afterrender事件,在callParent代码之前添加以下代码:
me.on("afterrender",me.onAfterRender);
接着要完成的就是onAfterRender方法了,在该方法内,主要工作是完成swfupload的初始化操作,代码如下:
onAfterRender: function(){
var me =this;
me.swfu= new SWFUpload({
upload_url: "/File/Upload",
file_size_limit: "10 MB",
file_types:"*.jpg;*.png;*.gif;*.bmp;",
file_types_description: "图片文件",
file_upload_limit: 100,
file_queue_limit: 0,
file_post_name: "Filedata",
swfupload_preload_handler: me.UploadPreLoad,
swfupload_load_failed_handler: me.UploadLoadFailed,
swfupload_loaded_handler: me.UploadLoaded,
file_queued_handler: me.fileQueued,
file_queue_error_handler: me.fileQueueError,
file_dialog_complete_handler: me.fileDialogComplete,
upload_start_handler: me.uploadStart,
upload_progress_handler: me.uploadProgress,
upload_error_handler: me.uploadError,
upload_success_handler: me.uploadSuccess,
upload_complete_handler: me.uploadComplete,
queue_complete_handler: me.queueComplete,
//Button settings
button_window_mode: SWFUpload.WINDOW_MODE.TRANSPARENT,
button_image_url: '',
button_placeholder_id: me.spanid,
button_width: 60,
button_height: 20,
button_text: '上传图片',
button_text_style: '',
button_text_top_padding: 0,
button_text_left_padding: 0,
//Flash Settings
flash_url: "Scripts/swfupload/swfupload.swf", // Relative tothis file
flash9_url: "Scripts/swfupload/swfupload_FP9.swf", // Relativeto this file
custom_settings: { scope: me },
//Debug Settings
debug: false
});
}
以下是swfupload的配置项的说明:
q upload_url:上传文件的地址,代码中是File控制器的Upload方法。
q file_size_limit:限制上传文件的大小,代码中限制了只能上传小于10M的文件。
q file_types:允许上传的文件类型,代码中允许的类型是jpg、png、gif和bmp格式的文件。
q file_types_description:这个是显示在文件选择对话框中的描述。
q file_upload_limit:一次允许上传的文件数量,这里设置一次最多只能上传100个文件。
q file_queue_limit:文件队列的限制,这里设置为0表示没有限制。
q file_post_name:文件提交后,服务器端可根据该参数获取文件。
q swfupload_preload_handler:监听预加载事件。
q swfupload_load_failed_handler:监听上传失败事件。
q swfupload_loaded_handler:监听上传成功事件。
q file_queued_handler:监听上传队列事件。
q file_queue_error_handler:监听队列错误事件。
q file_dialog_complete_handler:监听文件选择对话框关闭事件。
q upload_start_handler:监听开始上传事件。
q upload_progress_handler:监听上传进度事件。
q upload_error_handler:监听上传错误事件。
q upload_success_handler:监听上传成功事件。
q upload_complete_handler:监听上传完成事件。
q queue_complete_handler:监听上传队列完成事件。
q button_window_mode:按钮的样式,这里设置了窗口系统模式。
q button_image_url:按钮图片的路径,因为没有,所以设置了为空。
q button_placeholder_id:就是SPAN元素的id了。
q button_width:按钮的宽度,要比TextItem小点。
q button_height:按钮的高度。
q button_text:按钮显示的文本,这里要显示的是“上传图片”。
q button_text_style:按钮文本的样式,这里不需要。
q button_text_top_padding:按钮文本的顶部内补丁。
q button_text_left_padding:按钮文本的左边内补丁。
q flash_url:flash文件所在的路径。
q flash9_url:flash 9版本的flash文件所在路径。
q custom_settings:自定义配置,这里一定要添加scope配置项,且值为me,这样就可在swfupload的事件内找到扩展自身,从而使用扩展的属性和方法。
q debug:是否开启调试模式,false表示不开启。
现在要完成的就是swfupload的监听事件了,这个基本可从swfupload包中的示例代码复制过来,具体代码如下:
UploadPreLoad:function(){
},
UploadLoadFailed:function(){
},
UploadLoaded:function(){
},
fileQueued:function(){
},
fileQueueError:function(file,errorCode, message){
try{
vardlg=Ext.Msg.alert;
if(errorCode === SWFUpload.QUEUE_ERROR.QUEUE_LIMIT_EXCEEDED) {
dlg("选择的文件太多。\n一次最多上传100个文件,而你选择了" +message + "个文件。");
return;
}
switch(errorCode) {
caseSWFUpload.QUEUE_ERROR.FILE_EXCEEDS_SIZE_LIMIT:
dlg("文件超过了10M.");
this.debug("错误代码: File too big, File name: " +file.name + ", File size: " + file.size + ", Message: " +message);
break;
caseSWFUpload.QUEUE_ERROR.ZERO_BYTE_FILE:
dlg("不允许上传0字节文件。");
this.debug("ErrorCode: Zero byte file, File name: " + file.name + ", File size: "+ file.size + ", Message: " + message);
break;
caseSWFUpload.QUEUE_ERROR.INVALID_FILETYPE:
dlg("非法的文件类型。");
this.debug("ErrorCode: Invalid File Type, File name: " + file.name + ", File size:" + file.size + ", Message: " + message);
break;
default:
if(file !== null) {
dlg("未知错误。");
}
this.debug("ErrorCode: " + errorCode + ", File name: " + file.name + ", Filesize: " + file.size + ", Message: " + message);
break;
}
}catch (ex) {
this.debug(ex);
}
},
fileDialogComplete:function(numFilesSelected,numFilesQueued){
try {
if(numFilesQueued > 0) {
varme=this.customSettings.scope,sels=me.tree.getSelectionModel().getSelection(),path="/";
if(sels.length>0){
path=sels[0].data.id;
SimpleCMS.postParams.path=path;
this.setPostParams(SimpleCMS.postParams);
this.startUpload();
}
else{
Ext.Msg.alert("请先选择文件夹。");
}
}
}catch (ex) {
this.debug(ex);
}
},
uploadStart:function(file){
try{
varme=this.customSettings.scope;
me.progress.updateProgress(0);
me.progress.updateText("正在上传文件"+file.name+"...");
}
catch(ex) {}
returntrue;
},
uploadProgress:function(file,bytesLoaded, bytesTotal){
try{
varpercent = bytesLoaded / bytesTotal;
varme=this.customSettings.scope;
me.progress.updateProgress(percent);
me.progress.updateText("正在上传文件"+file.name+"...");
}catch (ex) {
this.debug(ex);
}
},
uploadError:function(file,errorCode, message){
try{
varme=this.customSettings.scope;
me.progress.updateText("正在上传文件"+file.name+"...");
switch (errorCode) {
caseSWFUpload.UPLOAD_ERROR.HTTP_ERROR:
me.progress.updateText("上传错误:" + message);
this.debug("ErrorCode: HTTP Error, File name: " + file.name + ", Message: " +message);
break;
caseSWFUpload.UPLOAD_ERROR.UPLOAD_FAILED:
me.progress.updateText("上传失败。");
this.debug("ErrorCode: Upload Failed, File name: " + file.name + ", File size: "+ file.size + ", Message: " + message);
break;
caseSWFUpload.UPLOAD_ERROR.IO_ERROR:
me.progress.updateText("Server(IO) 错误");
this.debug("ErrorCode: IO Error, File name: " + file.name + ", Message: " +message);
break;
caseSWFUpload.UPLOAD_ERROR.SECURITY_ERROR:
me.progress.updateText("安全错误");
this.debug("ErrorCode: Security Error, File name: " + file.name + ", Message: " +message);
break;
caseSWFUpload.UPLOAD_ERROR.UPLOAD_LIMIT_EXCEEDED:
me.progress.updateText("文件大小超出限制。");
this.debug("ErrorCode: Upload Limit Exceeded, File name: " + file.name + ", File size:" + file.size + ", Message: " + message);
break;
caseSWFUpload.UPLOAD_ERROR.FILE_VALIDATION_FAILED:
me.progress.updateText("验证失败。");
this.debug("ErrorCode: File Validation Failed, File name: " + file.name + ", Filesize: " + file.size + ", Message: " + message);
break;
caseSWFUpload.UPLOAD_ERROR.FILE_CANCELLED:
break;
caseSWFUpload.UPLOAD_ERROR.UPLOAD_STOPPED:
me.progress.updateText("停止");
break;
default:
me.progress.updateText("未知错误:" + errorCode);
this.debug("ErrorCode: " + errorCode + ", File name: " + file.name + ", Filesize: " + file.size + ", Message: " + message);
break;
}
}catch (ex) {
this.debug(ex);
}
},
uploadSuccess:function(file,serverData){
try{
varme=this.customSettings.scope;
me.progress.updateProgress(1);
me.progress.updateText(serverData);
}catch (ex) {
this.debug(ex);
}
},
uploadComplete:function(file){
try {
if(this.getStats().files_queued > 0) {
this.startUpload();
}else {
varme=this.customSettings.scope;
me.progress.updateProgress(1);
me.progress.updateText("所有文件已上传。");
me.filestore.load();
}
} catch(ex) {
this.debug(ex);
}
},
queueComplete:function(numFilesUploaded){
varme=this.customSettings.scope;
me.progress.updateProgress(1);
me.progress.updateText("已上传"+ numFilesUploaded + "个文件");
if(numFilesUploaded>0){
me.filestore.load();
}
}
代码中UploadPreLoad、UploadLoadFailed、UploadLoaded和fileQueued么有使用到,其实可以在配置中去掉,在这里写出来是为了说明一下。要具体了解清楚这些事件,可仔细阅读一下swfupload的API文档。
先来看看fileQueueError方法,该方法会在队列出现出错时触发。代码中,涉及dlg的代码是提示给用户看的,debug则是在开启了调试模式时使用的。
方 法fileDialogComplete会在文件选择对话框关闭后触发,在这里就意味着开始上传文件了,因而,当检测到队列中有文件 (numFilesQueued大于0),就从customSettings中获取扩展自身,然后从树中获取选择模型,看是否有选择节点,如果有,就把选 择节点(目录)作为上传路径,否则,提示用户选择一个节点(目录)。
在这里一定会很奇怪,为什么会有一个SimpleCMS. postParams的东西,它有什么用?这主要是验证问题,因为Flash上传并不会把当前页面的验证作为其验证,因而要在服务器端验证上传文件的用户 是否已经登录且符合权限要求,就要求通过添加验证方式办法来实现,它的具体代码如下:
SimpleCMS.postParams = {
path:null,
"ASPSESSID": "@Session.SessionID",
"AUTHID": "@Request.Cookies[FormsAuthentication.FormsCookieName].Value"
};
代 码中,path是用来设置上传路径的,后面两项就是用来填写验证信息用的。笔者在第一次做这个的时候,上传老是不成功,然后在调试模式下(设置debug 为true),看到的提示是权限不足,奇怪了,然后google一下,发现原来Flash上传文件的验证信息不能和页面的同步,要加这两个东西来实现。
要完成这个,还要打开Global.asax文件,在里面加入以下代码:
protected void Application_BeginRequest()
{
varRequest = HttpContext.Current.Request;
varResponse = HttpContext.Current.Response;
try
{
string session_param_name = "ASPSESSID";
string session_cookie_name = "ASP.NET_SESSIONID";
if(HttpContext.Current.Request.Form[session_param_name] != null)
{
UpdateCookie(session_cookie_name,HttpContext.Current.Request.Form[session_param_name]);
}
elseif (HttpContext.Current.Request.QueryString[session_param_name] != null)
{
UpdateCookie(session_cookie_name,HttpContext.Current.Request.QueryString[session_param_name]);
}
string auth_param_name = "AUTHID";
string auth_cookie_name = FormsAuthentication.FormsCookieName;
if(HttpContext.Current.Request.Form[auth_param_name] != null)
{
UpdateCookie(auth_cookie_name,HttpContext.Current.Request.Form[auth_param_name]);
}
elseif (HttpContext.Current.Request.QueryString[auth_param_name] != null)
{
UpdateCookie(auth_cookie_name,HttpContext.Current.Request.QueryString[auth_param_name]);
}
}
catch(Exception e)
{
Response.StatusCode = 500;
Response.Write("Error Initializing Session");
}
}
private static void UpdateCookie(stringcookie_name, string cookie_value)
{
HttpCookie cookie =HttpContext.Current.Request.Cookies.Get(cookie_name);
if(cookie == null)
{
cookie = new HttpCookie(cookie_name);
//SWFUpload 的Demo中给的代码有问题,需要加上cookie.Expires 设置才可以
cookie.Expires = DateTime.Now.AddYears(1);
HttpContext.Current.Request.Cookies.Add(cookie);
}
cookie.Value = cookie_value;
HttpContext.Current.Request.Cookies.Set(cookie);
}
其实这代码,在swfupload的示例中也是有的,只是笔者没留意。
回到fileDialogComplete方法,调用swfupload的setPostParams方法就可将参数复制到swfupload中了,然后调用startUpload方法就可以开始上传文件了。
方法uploadStart会在文件开始上传的时候执行,在这里要做的就是更新进度条了。(这里说明一下,swfupload是一个个文件传的,并不是一次把所有文件都传过去的)。
方法uploadProgress就是用来更新进度的,主要功能就是更新进度条了。
方法uploadError是用来显示上传错误的,复制过来根据自己想法修改提示方式就行了。
方法uploadSuccess会在一个文件上传成功后执行,这里要做的就是将进度条显示到100%,并显示服务器端返回的信息。
文件上传完也会执行uploadComplete方法,在这里可检查队列中是是否还有文件,如果有,就调用startUpload继续上传,如果没有,则更新进度条显示,说明文件已经全部上传完毕。
队列中的文件都上传后会执行queueComplete方法,这个和uploadComplete方法检查队列中没有文件后的处理有点重叠,看你怎么取舍了。
最后,别忘了在Index.html加入加载swfupload.js的代码,代码如下:
@if (Request.IsAuthenticated)
{
<script type="text/javascript"src="@Url.Content("Scripts/swfupload/swfupload.js")"></script>
}
这个脚本只有在登录后才会加载。
现在切换到File控制器,完成Upload方法,这个不难,就是接收一个文件,然后保存而已,代码如下:
[AjaxAuthorize(Roles = "普通用户,系统管理员")]
public string Upload()
{
stringpath = Request["path"] ?? "";
string[]filetypes = { "jpg", "gif", "png","bmp" };
stringdir = Server.MapPath(root + path);
if(Directory.Exists(dir))
{
HttpPostedFileBase file = Request.Files["Filedata"];
string filename = file.FileName.ToLower();
string extname = filename.Substring(filename.LastIndexOf(".")+ 1, 3);
if(file.ContentLength <= 0)
{
return "不允许上传0字节文件。";
}
elseif (file.ContentLength > 10485760)
{
return "文件超过了10M。";
}
elseif (!filetypes.Contains(extname))
{
return "文件类型错误。" + extname;
}
else
{
try
{
file.SaveAs(dir + "\\" + filename);
return "文件已上传。" + dir + "\\" + newfilename;
}
catch (Exception e)
{
return e.Message;
}
}
}
else
{
return "保存目录不存在或已被删除。";
}
}
代码没有对重名文件做检查,这个有需要可以自己加上去。
现在可以测试一下了。生成解决方案后,在浏览器打开首页,将看到如图38所示的效果,在视图底部已经有了一个上传图片按钮(实际效果不像按钮,如果想类似按钮,最好是做一下图片)和进度条了。切换到目录1,然后单击上传图片按钮,上传一下图片后将看到如图39所示的效果。
图38 添加了上传图片按钮和进度条的图片管理界面
图39 上传文件后的显示
最后讨论一下扩展的Store问题,如果想所有的扩展都共享一个TreeStore和视图的Store,可以把它们独立出来,写到App的Store目录里,这样就可共享了。这还是个人喜好问题。
至此,图片管理的功能就完成了,余下就是文章管理的功能了。
源代码: http://vdisk.weibo.com/s/hcL2Y