Asp.Net MVC2 控件开发实例(1)
Asp.Net MVC2 控件开发实例(2)
这次是一个图片控件,实现轮播、上传、删除、修改以及点击时放大等功能。 先来看最终效果:
说明:轮播的CSS参考了网上一篇文章,具体链接忘记了,其余为原创。
书归正传。这个控件有几个功能点,1是无刷新上传和修改,这里使用了一个上传插件,详见:
ajaxupload。官方有使用说明,这里我就不讨论了;2是新增修改和删除图标的定位问题,CSS设置定位和层叠即可,也不细说了;3是点击小图显示大图的问题,这里面还有缩略图问题;4是控件单独刷新。以下将依次说明。
首先构建基本的HTML和CSS:
HTML+CSS
<
div
class
="banner_f"
>
<
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').load('/FM/Modify/BannerPhoto', { YWDTID: dtid, timeStamp: time,width:'450',height:'600' }, function () {
fillRYPhoto($('#ZPDTID').val() == null ? '' : $('#ZPDTID').val(), false, 0);
}); 其中tdPhoto是需要填充此图片控件的控件ID,'/FM/Modify/BannerPhoto'为请求的action(此action作用为返回此图片控件的PartialViewResult),YWDTID是这些图片的数据源ID,timeStamp为时间戳以避免缓存问题,width为显示大图时的外围层宽度,height为大图外围层高度(或者说是想显示大图的最大宽度和高度以便自适应尺寸来保证图片不变形),回调方法fillRYPhoto填充图片。
首先是action,作用就是绑定一些页面数据并返回PartialViewResult:
View Code
[HttpPost()]
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里面只包含了外层控件,所以这里面做的事情比较多,包括所有内层控件的回填:
<
script type
=
"
text/javascript
"
>
//
#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
public
static
Image ConvertToImg(Byte[] byte_img)
{
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。小图点击时直接请求数据库原图并将需显示大小尺寸做入参在后台计算并处理生成缩略图(这里的缩略图生成比较简单,没有做插值算法处理,有兴趣可以自行生成高质量缩略图):
View Code
public
FileResult GetRyPhotoHDByID(
string
ZPDTID,
int
hd_width,
int
hd_height,
string
timestamp)
{
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时清除计时器:
View Code
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
);
其余的新增,删除修改图片的后台方法比较简单我就不贴了。各位看官有何意见,尽管拍砖~