Asp.Net MVC2 控件开发实例(3)
这次是一个图片控件,实现轮播、上传、删除、修改以及点击时放大等功能。 先来看最终效果:
说明:轮播的CSS参考了网上一篇文章,具体链接忘记了,其余为原创。
书归正传。这个控件有几个功能点,1是无刷新上传和修改,这里使用了一个上传插件,详见:
ajaxupload。官方有使用说明,这里我就不讨论了;2是新增修改和删除图标的定位问题,CSS设置定位和层叠即可,也不细说了;3是点击小图显示大图的问题,这里面还有缩略图问题;4是控件单独刷新。以下将依次说明。
首先构建基本的HTML和CSS:
<div class="banner">
<div class="banner_bg">
</div>
<!--标题-->
<div class="banner_info">
</div>
<!--标题背景-->
<div class="banner_list">
</div>
<div class="div_imgHD">
</div>
</div>
<ul class="banner_ul">
</ul>
</div>
<%=Html.Hidden("ZPDTID", Model.ZPDTID as string)%>
<%=Html.Hidden("PhotoYWdtid", Model.PhotoYWdtid as string)%>
<%=Html.Hidden("HD_Width", Model.HD_Width as string)%>
<%=Html.Hidden("HD_Height", Model.HD_Height as string)%>
<style type="text/css">
.banner_f
{
height: 140px;
width: 91px;
position: relative;
border: 1px solid #666;
}
.banner
{
position: relative;
width: 90px;
height: 120px;
border: 1px solid #666;
overflow: hidden;
}
.banner_list img
{
border: 0px;
height: 120px;
width: 90px;
}
.banner_list
{
float: left;
}
.div_imgHD
{
display:none;
}
.banner_bg
{
position: absolute;
bottom: 0;
background-color: #ffffff;
height: 20px;
filter: Alpha(Opacity=30);
opacity: 0.3;
z-index: 1000;
cursor: pointer;
width: 90px;
}
.banner_info
{
position: absolute;
bottom: 0;
left: 15px;
height: 15px;
color: #fff;
z-index: 1001;
cursor: pointer;
left:30%;
}
.banner_text
{
position: absolute;
width: 90px;
z-index: 1002;
right: 3px;
bottom: 3px;
}
.banner_ul
{
position: absolute;
list-style-type: none;
filter: Alpha(Opacity=80);
opacity: 0.8;
border: 1px solid #fff;
z-index: 1002;
margin: 0;
padding: 0;
bottom: 0;
right: 5px;
height: 17px;
}
.banner_ul li
{
padding: 0px 4px;
float: left;
display: block;
color: #FFF;
border: #e5eaff 1px solid;
background-color: #6f4f67;
cursor: pointer;
width: 3px;
}
.banner_list a
{
position: absolute;
}
</style>
简单说明一下:banner_f是整个控件的外层div,高度为图片高度120+导航高度20=140,宽度为图片宽度90+border1=91,相对定位;banner为图片div;banner_list为图片div;div_imgHD为显示大图的div(初始为隐藏);banner_ul为导航。
使用的时候这样即可:
}); 其中tdPhoto是需要填充此图片控件的控件ID,'/FM/Modify/BannerPhoto'为请求的action(此action作用为返回此图片控件的PartialViewResult),YWDTID是这些图片的数据源ID,timeStamp为时间戳以避免缓存问题,width为显示大图时的外围层宽度,height为大图外围层高度(或者说是想显示大图的最大宽度和高度以便自适应尺寸来保证图片不变形),回调方法fillRYPhoto填充图片。
首先是action,作用就是绑定一些页面数据并返回PartialViewResult:
public PartialViewResult BannerPhoto(string YWDTID, string timeStamp,string width,string height)
{
string ZPDTIDList = string.Empty;
DataTable zpDt = GetZpDt(YWDTID, out ZPDTIDList);
dynamic m = new System.Dynamic.ExpandoObject();
m.ZPDTID = ZPDTIDList;
m.PhotoYWdtid = YWDTID;
m.HD_Width = width;
m.HD_Height = height;
return PartialView("~/Views/Shared/BannerPhoto.ascx", m);}
然后是回调方法 fillRYPhoto,由于初始的html里面只包含了外层控件,所以这里面做的事情比较多,包括所有内层控件的回填:
//#region fillRYPhoto
function fillRYPhoto(IDList, hidButton, index) {
if (index == null || index == 'undefined') {
index = 0;
}
$.unblockUI;
var list = IDList.toString().split(',');
$('.banner_ul').eq(index).empty();
$('.banner_list').eq(index).empty();
for (var i = 0; i < list.length; i++) {
$('.banner_ul').eq(index).append("<li>" + (i + 1).toString() + "</li>");
$('.banner_list').eq(index).append("<a href='javascript:///'><img title='点击放大' id='"+ list[i] + "' onclick=\"showHDimg(this.id,"+index+");\" src=\"/FM/Modify/GetRyPhotoByID?ZPDTID='" + list[i] + "'×tamp=" + new Date() + "\" /></a>");
}
if ($('.banner_ul').eq(index).children('li').length < 2) {
$('.banner_ul').eq(index).remove();
$('.banner_f').eq(index).css('height', '120px');
}
else {
$('.banner_ul').eq(index).children('li').show();
}
bindZPClick(hidButton,index);
}
//#endregion
//#region showHDimg:显示大图
function showHDimg(imgID,index) {
var selector = '.banner_list:eq(' + index + ')' + ' img';
if ($(selector).attr('id').length == 0) { return };
var hd_width = $('#HD_Width').val();
var hd_height = $('#HD_Height').val();
$('.div_imgHD').eq(index).empty().append("<img id='" + imgID + "_HD'" + " src=\"/FM/Modify/GetRyPhotoHDByID?ZPDTID='" + imgID + "'&hd_width=" + hd_width + "&hd_height=" + hd_height + "×tamp=" + new Date() + "\">");
$.blockUI({
message: $('#' + imgID + '_HD').click($.unblockUI),
css: {
centerY: true,
top: ($(window).height() - hd_height) / 2 + 'px',
left: ($(window).width() - hd_width) / 2 + 'px',
width: hd_width + 'px',
height: hd_height + 'px'
},
fadeIn: 700,
fadeOut: 700
});
$('.blockOverlay').attr('title', '点击收起').click($.unblockUI);
var i = setInterval(function () {
var h = $('#' + imgID + '_HD').height();
$('#' + imgID + '_HD').css('margin-top', (hd_height - h) / 2 + 'px');
if (h > 0) {
clearInterval(i);
}
}, 1000);
}
//#endregion
//#region 绑定事件
function bindZPClick(hidButton,index) {
var count = $(".banner_list").eq(index).children('a').length;
$(".banner_list").eq(index).children('a:not(:first-child)').hide();
editBannerinfo(hidButton,index);
$(".banner_ul").eq(index).children('li').click(function () {
var i = $(this).text() - 1; //获取Li元素内的值,即1,2,3,4
if (i >= count) return;
$(".banner_list").eq(index).children('a').filter(":visible").fadeOut(500).parent().children().eq(i).fadeIn(1000);
$(this).css({ "background": "#be2424", 'color': '#000' }).siblings().css({ "background": "#6f4f67", 'color': '#fff' });
});
}
function editBannerinfo(hidButton, index) {
if (hidButton == 'undefined' || hidButton) {
hidButton = false;
$('.banner_bg').eq(index).remove();
}
else {
$(".banner_info").eq(index).empty();
if ($('.banner_ul').eq(index).children('li').length > 4) {
$(".banner_info").eq(index).append("<image alt='新增' id='" + index + "_imgRyAdd' src='/FM/Content/Images/plus.jpg' onclick='$.growlUI(\"最多只能上传5张照片\",null,1000);$(\"div.growlUI\").attr(\"class\",\"growlUI_Error\");' />");
}
else {
$(".banner_info").eq(index).append("<image alt='新增' id='" + index + "_imgRyAdd' src='/FM/Content/Images/plus.jpg' />");
}
$(".banner_info").eq(index).append("<image alt='删除' id='" + index + "_imgRyDel' src='/FM/Content/Images/minus.jpg' onclick='delZP(" + index + ");' />")
.append("<image alt='修改' id='" + index + "_imgRyModify' src='/FM/Content/Images/edit2.png' />");
setTimeout(function () {
setRyUpload('add', index);
setRyUpload('modify', index);
}, 3000);
}
}
//#endregion
//#region 初始化人员上传控件
function setRyUpload(operation, index) {
var url;
var mess;
var selector;
var setdata;
var zpid = $(".banner_list").eq(index).children('a').filter(":visible").children().attr("id");
var content = '';
if (operation === 'add') {
url = '/FM/Modify/AddRyImg';
mess = '新增图片成功!';
content = '相片ID:';
selector = '#'+index+'_imgRyAdd';
setdata = { 'YWDTID': $('#PhotoYWdtid').val() };
}
else {
url = '/FM/Modify/ModifyRyImg';
mess = '修改图片成功!';
content = '相片ID:' + zpid;
selector = '#' + index + '_imgRyModify';
setdata = { 'photoID': zpid };
}
var upload = new AjaxUpload(
$(selector),
{
action: url,
onSubmit: function () { this.disable(); },
responseType: "json",
onComplete: function (file, response) {
refreshTB(index);
if (response == '1') {
$.growlUI(mess, null, 2000);
}
else {
$.growlUI('上传失败', null, 2000);
}
}
});
upload.setData(setdata);
}
//#endregion
//#region 删除照片
function delZP(index) {
if (window.confirm("确认删除当前照片吗?")) {
var zpid = $(".banner_list").eq(index).children('a').filter(":visible").children().attr("id");
var mess;
$.post(
'/FM/Modify/DltRyImg', { photoID: zpid },
function (result) {
mess = result == 1 ? '删除成功' : '删除失败';
$.growlUI(mess, '相片ID:' + zpid, 2000);
refreshTB(index);
}
);
}
}
//#endregion
//#region 刷新照片
function refreshTB(index) {
var ywdtid = $('#PhotoYWdtid').val();
var time = (new Date()).toString();
var hd_width = $('#HD_Width').val();
var hd_height = $('#HD_Height').val();
$('.banner_f').eq(index).parent().empty().load('/FM/Modify/BannerPhoto',
{ YWDTID: ywdtid, timeStamp: time, width: hd_width, height: hd_height },
function () {
fillRYPhoto($('#ZPDTID').val(), false, index);
});
}
//#endregion
</script>
这里详细说一下显示大图的问题,由于图片在数据库里是以blob的形式存储,又担心图片的文件存储问题,所以自始自终图片都不以文件的形式存储,而用户又要求显示大图的时候要自适应宽度和高度,也就是不能变形又不能溢出自定义的外框,这样就要进行图片处理。下面是byte[]和 Image互转的两个方法:
View Code
{
using (MemoryStream stream = new MemoryStream(byte_img))
{
Image img = Image.FromStream(stream);
return img;
}
}
public static Byte[] ConvertToByte(Image img)
{
using (MemoryStream stream=new MemoryStream())
{
img.Save(stream, ImageFormat.Jpeg);
return stream.ToArray();
}
}
显示大图时我用了blockUI,用法见blockUI。小图点击时直接请求数据库原图并将需显示大小尺寸做入参在后台计算并处理生成缩略图(这里的缩略图生成比较简单,没有做插值算法处理,有兴趣可以自行生成高质量缩略图):
{
DataTable dt = Session["zp"] as DataTable;
if (dt == null)
{
return File(Server.MapPath("~/Content/Images/nophoto.jpg"), "application/octet-stream");
}
DataRow[] dr = dt.Select("DTID=" + ZPDTID);
if (dr.Count() < 1)
{
return File(Server.MapPath("~/Content/Images/nophoto.jpg"), "application/octet-stream");
}
Byte[] zpnr = (Byte[])dr[0]["ZPNR"];
Image img = ImageCommon.ConvertToImg(zpnr);
double a = Convert.ToDouble(img.Width) / Convert.ToDouble(img.Height);//原图宽高比
double b = Convert.ToDouble(hd_width) / Convert.ToDouble(hd_height);//浮动层宽高比
double w = img.Width;//显示的大图的宽度
double h = img.Height;//显示的大图的高度
if (img.Width > hd_width || img.Height > hd_height)//原图的宽度或高度大于外层宽高则做缩略图,否则显示原图
{
if (a > b)
{
w = hd_width;
h = Convert.ToDouble(w * img.Height) / Convert.ToDouble(img.Width);
}
else
{
h = hd_height;
w = Convert.ToDouble(h * img.Width) / Convert.ToDouble(img.Height);
}
Image.GetThumbnailImageAbort myCallback = new Image.GetThumbnailImageAbort(CallBack);
Image i = img.GetThumbnailImage(Convert.ToInt32(w), Convert.ToInt32(h), myCallback, IntPtr.Zero);
Byte[] byte_img = ImageCommon.ConvertToByte(i);
return File(byte_img, "application/octet-stream");
}
else
{
return File((Byte[])dr[0]["ZPNR"], "application/octet-stream");
}
}
private static bool CallBack() //委托方法
{
return false;
}
生成之后有一个定位问题,因为返回之后要做居中处理(大图要显示在自定义的层中央,这个自定义层的大小是根据业务自定义的,主要是显示大图的时候用户可能还想看前面的信息),但是图片此时虽然已经生成,但是前台缺未必可以获取到尺寸,这里用了一个小技巧,用一个计时器反复检查图片尺寸直到尺寸大于0时清除计时器:
var h = $('#' + imgID + '_HD').height();
$('#' + imgID + '_HD').css('margin-top', (hd_height - h) / 2 + 'px');
if (h > 0) {
clearInterval(i);
}
}, 1000);
其余的新增,删除修改图片的后台方法比较简单我就不贴了。各位看官有何意见,尽管拍砖~