SQL Server 2012 T-SQL 新特性
NoSQL之HBase
9月初淘宝飞芃做了一个关于HBase的分享,讲的激情飞扬,让听众收益匪浅,现做下简单总结。
HBase是一个NoSQL数据库,在国内外被广泛使用,是bitTable的开源实现,主要由FaceBook、Cloudera、Yahoo!和ebay等大公司贡献,国内贡献主要是华为和阿里系,其中淘宝还有一个代码commiter。飞芃首先对HBase做了简要介绍,着重强调它的一些特点。之后又讲了Hbase不好的地方,以及使用的注意点,最后介绍了HBase在淘宝的一些应用。
1. HBase的特点
a)强一致性:同一行数据读写在同一个regionserver上
b)水平伸缩:region的自动分裂以及master的balance
c)行事务:同一行的写入是原子的,这是由于同一行数据读写在同一个regionserver上决定的
d)支持范围查询
e)高性能随即写
上面的特点让我觉得HBase是Hash和树的这两种数据结构的结合体。首先HBase使用了Hash,比如根据rowkey获得value,还使用了树,正因为是棵树,所以具有分裂、支持范围查询等特性。
2.HBase不好的地方
a)没有二级索引,使用时需要将自己写索引表,这个索引表也存在HBase里
b)没有在线备份与还原策略
c)不稳定,使用有风险,比如NameNode单点,一旦挂了,整个HBase集群挂了,RegionServer挂掉后需要一些时间切换,Region分裂时服务不可用
3.HBase在淘宝的应用
个人感觉是最精华的部分,HBase在淘宝里用在三个地方:
a)实时推荐、实时报表、实时计费
这类应用的特点是大量数据的实时写入以及读取
b)大数据量类型项目
比如历史类或需要长期保存的数据
c)二次分析类型项目
Hadoop集群做粗粒度分析,在线做二次分析,比如数据魔方。
4.讨论
会后讨论的时候,飞芃着重强调了HBase的不稳定,需要专业团队维护,比如淘宝有一百多人的技术团队专门维护Hadoop和HBase集群,而且使用淘宝内部的HBase版本。针对一些同学提出的业务需求是否适合HBase的问题,飞芃建议要综合考虑应用规模等因素,但是如果选择了HBase,必须要有团队专门维护。
O2O项目变态需求:求华山论剑,分析建模,指点迷津
分享一个做过的变态需求,闲话少许,书回正文:
话说小弟以前常做B2C网站及系统,对此已小有心得,一日经理曰:今又接一项目,乃O2O也,与常做B2C无他异也,但做无妨,然时间较紧,只有3月也;
闻此,小弟心中一亮,因为
1:O2O最近话题比较火,这是第一次做O2O项目,有希望,也有挑战;
2.希望套用之前自己做B2C的思想,建一个优雅,干净的系统;
然,小弟不知一股难以驾驭的黑风已悄然袭来。。。
一:项目概述:
现捡主要功能模块进行分析:
这是一个全国性质的4S,汽车保养分店系统:可参照:http://www.yangche51.com/
B2C商品服务模块:
常见的商品在线商城系统,但会依据用户选择的车型+排量,推荐用户对应商品,不符合用户选择的车型+排量的商品不允许加入购物车及购买,但允许收藏;
如果前台用户未选择车型+排量,则用户可以加入购物车;
在购物车转换成订单时必须引导用户前台选择车型+排量,并对用户选择的商品是否适合当前选择车型+排量进行判断,不匹配则剔除商品并引导用户重新购买“类似”商品;
并在订单产生及支付之前,选择车型+排量随时可以修改:
O2O服务项模块:
此模块分四大部分:日常保养,汽车快修,美容装饰,钣金喷漆;
在这里方便起见,说说日常保养:
1.服务在线选择逻辑流程
|
前台 |
依据 |
后台 |
1 |
用户是否登录 |
用户历史保养记录 服务项目特别产品表(注一) |
获取用户各个项目上次保养时间 + 项目周期(注一); |
2 |
输入车型、车况、排量+变速箱及发动机型号 |
车型车况对应项目表 |
获得对应的首保时间(注二) + 项目服务周期 |
3 |
输入行驶里程和购买时间 |
服务项目和产品计算办法及流程(本文公式) |
获得服务项目列表 |
4 |
|
产品适配表 第五层问题(如轮胎) |
服务项目对应的产品 |
5 |
|
服务项目工时费表 服务产品价格表 车型加价系数表 报价原则(工时费+采购价格*车型对应加价系数) |
服务项目的价格 |
6 |
用户选择服务项目和更换产品功能(注三) |
|
|
7 |
形成订单 |
|
|
注一:若用户上次保养时使用会影响默认保养周期的特别产品,如全合成机油、泊金火花塞等,则应保存这个项目的服务周期,如:上海大众,CROSS波罗,1.6L 5MT,依铂金火花塞,服务周期90000/72。以便在后面的步骤中,替换对应的默认服务周期;
注二:对于有历史保养项目的记录,首保时间可替换概念为“上次保养时间”;
注三:用户选择服务项目、更换产品、显示价格,简化成在一个页面完成,如下图:
首保只针对“更换发动机油”和“更换机油滤清器”两个项目,即此二项目使用公式时需要减去首保里程(或时间),其它项目不减或减零。
再次说明,对于有历史保养记录的项目,首保里程(或时间)即为用户的上次保养里程(或时间),此项内容在以后的文字或公式中不再作说明;
2.保养涉及的表及基础文档信息:
车品牌表
车系表
车配置排量表
日常保养下的服务项目表
商品表
商品车配置表
商品,服务,车配置商品表,对应的是商品编号:
特殊服务商品推荐表:如用户上一次火花塞服务选用的为依波金或双铂金,则此次用户计算出是否需要购买火花塞服务,则优先推荐并依托公式计算是否需要依波金或双铂金(这里就不给用户”更换配件“的功能了),否则按正常流程按普通火花塞走;
日常服务车况表:包含行驶里程,维修间隔(当前时间-购买时间,或,上次维修时间(在系统维修记录里可以找到)),依照公式:
同一辆车会因为上次同一种服务商品的不同有不同的首保时间,里程供公式使用:
服务问题商品表:
选择产品时第五层问题以轮胎选择举例如下:
品牌-车型-发动机排量/变速箱型号-发动机型号(生产起止年份):
以上信息系统无法推荐合适的轮胎产品, 自动弹出一个问题:
请车主检查轮胎记录下前后轮胎的规格并填写后系统才能推荐合适的产品?
因为同一型号的汽车轮胎规格不同,为了准确推荐轮胎,请您写下(打勾)轮胎规格如下:
前胎规格:215/60 R 17 210/65 R17 205/70 R18
后胎规格:225/65 R 17 210/65 R17 205/70 R18
(轮胎尺寸选择项来自3.9)
客户填写后系统马上推荐产品如下:
品牌: 固铂 米其林 大陆 倍耐力
速度级别:(附带一个速度级别表作解释)
95V 95H
经济型 高里程 高性能 超高性能 操控
前胎 产品1 产品2 产品3 产品4 产品5
后胎 产品1 产品2 产品3 产品4 产品5
其中绿色分类基于后台逻辑(分类:PCR或者SUV),参见文件1和3.9
PCR: 经济型、高里程、高性能、超高性能、操控
SUV: 全路况、非公路、公路、
客户选择完成后所选产品后自动进入下一环节(选择施工店-购物篮-付款等)
客户回答问题向导提示:车主请抄下车辆轮胎规格的前9个符号即可
公式:
1、里程选择法
(实际里程-首保公里) % 项目里程间隔M = 余数R
注:首保公里仅在“更换发动机油”和“更换机油滤清器”两个项目中有效,在计算其它项目时,首保公里为零,下同;
当R<=1000 或 M-R<=1000时选择对应的服务项目。
2、时间选择法(以月为单位)
(实际时间-首保时间) % 项目时间间隔T = 余数R
当R<=1或T-R<=1 时,选择对应的服务项目。
3、服务项目选择原则
3.1当二种计算方法符合时,选择为重要项目(推荐项目再加星号以示区别);
3.2当其中一个条件符合另一条件不符合时,同样选择该服务为推荐项目;
3.3当二个条件都不符合时,不选择。
3.页面流程
- 依照用户前台选择的车型+排量及用户填写的车况及用户提供的公式进行判断当前用户,当前车所需做的服务项目:
2.得到服务项目列表:
需求是列表中展示出适配车型+配置的服务及服务对应的商品,没有商品则在这里提供对应的“问题”,依照用户选择的问题显示适配商品集合;
商品显示约束:同一分类下商品默认显示一个,(机油->2L机油),点击商品后面的“更换配件”加载其他商品;
如果列表中服务项目既没有商品,也没有问题,并且服务不是(“维修”分类下的服务则不予显示),原文:服务不挂钩商品,显示没有意义,但“维修”分类下的服务需要在维修过程中确定商品,所以可以没有商品
最后出现的项目结果如下:
保养项目及养护项目 |
里程或时间间隔 以先到指标为准 |
计算结果 |
要做(Y) 不做(N) |
更换发动机油 |
7500KM或6个月 |
|
Y(重要) |
更换机油滤清器 |
7500KM或6个月 |
|
Y(重要) |
更换空气滤清器 |
22500KM或18个月 |
|
Y |
更换燃油滤清器 |
22500KM或18个月 |
|
Y |
更换防冻液 |
30000KM或24个月 |
|
N |
3.不同的车对应同一个服务项目,所需的工时,价格是不同的:
项目不同地区的折扣不一样:
4.订单生成并提交,当后台订单处理完成后则生成用户保养记录,支付方式分在线支付及到店支付;
总结:
1.首先感受到了这个项目和传统B2C项目的区别,商品本身的属性状态不像之前的那么简单了,而是会随着全国不同地区,不同分店而不一样,状态也不一样:比如价格,库存等;这些都是以前自有库存所不具备的;
2.对于汽车装饰,美容保养的不熟悉,甚至不清楚车系,配置,变速箱,排量之间的关系,于是难以在头脑中形成有效的模型;
3.需求的不稳定,及变化;经过中间的几次沟通与了解,发现产品那边过来的需求和用户想要的完全不一样,开发与客户之间隔了不懂技术,不会建立模型的产品的话,感觉百害而无一例;如此多的需求,在首期开发过程的需求当中,提到的不及1/10,以至于后期很多模块要推倒重建;
4.真正落实需求建模,正是由于前期需求不稳定,导致后期维护对应模块时更改较多,体现在数据库表设计上就是关系表太多,导致系统计算查询上效率不高;
5.外包项目难以逾越的还是时间,3个月最后看了完全不够;
6.有一个技术能力好的上级是多么幸福,可是我这次没有感受到,这次项目组长提供的建议不管是技术上还是业务上都没能给予好的帮助和支持,主要体现在与客户视频会议上,他都不知道用某些功能模块,并且他建议的功能模块开发出来并未得到客户的肯定(客户说想要的不是这样的,怀疑是否与客户求证过),最后领导追究责任只能到我们身上,因为组长还有其他的项目;
7.系统模块开放前一定要得到客户的确认,否则只能是需求改了又改,加班只有开发知道;
8:外包害死人啊,
序列 Sequence
SQL Server 现在将序列当成一个对象来实现,创建一个序列的例子语法如下:
CREATE SEQUENCE DemoSequence START WITH 1 INCREMENT BY 1;
使用序列的方法如下所表达的:
SELECT VALUE FOR DemoSequence
序列与以前的种子列(identity)的区别很明显,种子列只限于当前列,而序列是一个对象层面的实现,则可以在多个表之间共享。这一点特点在管理软件序列号生成方面,是个不错的开始。和种子列相似,序列也可以重置,例子如下
ALTER SEQUENCE Samples.IDLabel RESTART WITH 1 ;
序列的值可以使用整型类型,比如tinyint, smallint, int, bigint, decimal 或是小数精度为0的数值类型。
序列的限制(limitation)有二个,一是序列不支持事务,即使事务中进行了回滚(rollback)操作,序列仍然返回下一个元素。
第二,序列不支持SQL Server 复制(replication),序列不会复制到订阅的SQL Server实例中。如果一个表的默认值依赖一个序列,而序列又是不可复制的,这会导致订阅的SQL Server出现脚本错误。
数据分页 Page Data
SQL Server一直在改善数据分页方法,SQL Server 2005内置的row_number函数可以实现,例子代码如下
SELECT * FROM ( SELECT ROW_NUMBER() OVER(ORDER BY CustomerID) AS sequencenumber, * FROM Customers) AS TempTable WHERE sequencenumber > 10 and sequencenumber <= 20
SQL Server 2012有更简洁的语法,例子代码如下所示
SELECT * FROM Customers ORDER BY CustomerID OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
依据客户编号排序,跳过前面10笔记录,取第10笔记录。这很像Linq中的Skip.Take,Linq语法例子如下
var customers=customerList.Skip(10).Take(10);
异常处理 Exception Handling
SQL Server 2005引入了类似于.NET语言的异常处理机制到T-SQL代码中,请参考下面的例子
BEGIN TRY BEGIN TRANSACTION – Start the transaction -- Delete the Customer DELETE FROM Customers WHERE EmployeeID = ‘CACTU’ -- Commit the change COMMIT TRANSACTION END TRY BEGIN CATCH -- There is an error IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION -- Raise an error with the details of the exception DECLARE @ErrMsg nvarchar(4000), @ErrSeverity int SELECT @ErrMsg = ERROR_MESSAGE(), @ErrSeverity = ERROR_SEVERITY() RAISERROR(@ErrMsg, @ErrSeverity, 1) END CATCH
如果在catch语句块中捕获了异常,只能引用RaiseError函数来继续抛出异常。新版本的SQL Server引入了throw关键字,可取代RaiseError函数的作用。参考代码如下
BEGIN TRY BEGIN TRANSACTION -- Start the transaction -- Delete the Customer DELETE FROM Customers WHERE EmployeeID = ‘CACTU’ -- Commit the change COMMIT TRANSACTION END TRY BEGIN CATCH -- There is an error ROLLBACK TRANSACTION -- Re throw the exception THROW END CATCH
异常处理机制的一个好处是N层回滚(rollback),抛出异常的程序,逐层向上寻找,直到找到处理异常的代码。
存储过程执行改善 Execute Procedure Enhanced
在旧的SQL Server版本中,要返回一个查询语句的列信息,可以使用SET FMTONLY语句,它返回结果列,而不是实际的数据,请参考运行下面的语句:
SET FMTONLY ON; GO SELECT * FROM dbo.GBITEM GO SET FMTONLY OFF;
存储过程是一个预编译的批处理语句块,预编译可改善性能,前一个版本的SQL Server应用关键字(WITH RECOMPILE)
可以强制重新编译存储过程,生成新的执行计划。新版本的SQL Server改善了查询结果的返回信息,可以对存储过程的查询结果,进行别名字义。下面的代码,重新定义存储过程的返回列信息:
EXEC CustOrderDetail ‘2’ WITH RESULT SETS ( ( ProductName1 varchar(100), Unitprice1 varchar(100), Quantity1 varchar(100), Discount1 varchar(100), ExtendedPrice1 varchar(100) ) );
Exec的参数With Results Set可以依据存储过程的实际返回结果,重新定义返回的列名或类型。参考下面的SQL语句:
CREATE PROCEDURE Denali_WithResultSet AS BEGIN SELECT 1 as No,’Tsql’ Type, ‘WithResultSet’ AS Feature UNION ALL SELECT 2 as No,’Tsql’ Type, ‘Throw’ AS Feature UNION ALL SELECT 3 as No,’Tsql’ Type, ‘Offset’ AS Feature UNION ALL SELECT 4 as No,’Tsql’ Type, ‘Sequence’ AS Feature END GO EXEC Denali_WithResultSet WITH RESULT SETS ( ( No int, FeatureType varchar(50), FeatureName varchar(50) ) )
上面的代码演示了如何运用with result set来修改存储过程的返回列名称。这个特性与第三方的工具集成,比如SSIS任务,报表中会有一定的改善作用。
元数据函数 Metadata Function
新版本的SQL Server增加了几个存储过程用于获取SQL Server 对象的元数据,比如下面的SQL语句:
EXEC sp_describe_first_result_set @tsql=N'SELECT * FROM gbitem'
它会返回表gbitem的每一列的元数据信息,比如列名,是否可空,数据类型,排序等数据信息。
下面的例子演示了如何应用上面提到的函数,返回存储过程的元数据:
CREATE PROC Production.TestProc AS SELECT Name, ProductID, Color FROM Production.Product ; SELECT Name, SafetyStockLevel, SellStartDate FROM Production.Product ; GO SELECT * FROM sys.dm_exec_describe_first_result_set ('Production.TestProc', NULL, 0) ;
此函数还可以返回多个SQL批处理查询的元数据信息,请参考下面的例子代码
SELECT * FROM sys.dm_exec_describe_first_result_set( N'SELECT CustomerID, TerritoryID, AccountNumber FROM Sales.Customer WHERE CustomerID = @CustomerID; SELECT * FROM Sales.SalesOrderHeader;', N'@CustomerID int', 0) AS a; GO
如果一个存储过程想返回多笔记录集,在旧版本的SQL Server中,只能获取最后一次返回的记录集。新版本的SQL Server对此作出一些改善,可以指定要返回的记录集。
CREATE PROC TestProc2 AS SELECT object_id, name FROM sys.objects ; SELECT name, schema_id, create_date FROM sys.objects ; GO SELECT * FROM sys.dm_exec_describe_first_result_set_for_object(OBJECT_ID('TestProc2'), 0) ; SELECT * FROM sys.dm_exec_describe_first_result_set_for_object(OBJECT_ID('TestProc2'), 1) ; GO
这个特性会给程序处理上带来很多便利,为返回二个结果集而不必定义二个重复的存储过程,而仅仅是返回的结果不同。
SQL 函数 SQL Function
新版本的SQL Server增加了很多函数,请参考园友的文章SQL Server 2012新增的内置函数尝试
这些函数的到来,可以给SQL编程带来便利性。不过,我以为自从SQL Server 2005引入了CLR,实现这些函数都相当容易,直接对.NET BCL一层简单的封装即可,不知道为何过了二个重要的版本后(SQL Server 2008,SQL Server 2008 R2),才加入这些基础函数。
关于SQL Server 2012 T-SQL方面更多的特性,请参考这里:
http://dattatreysindol.com/2012/07/30/sql-server-2012-transact-sql-enhancements-learning-resources/
Jquery给我们提供了很大的方便,我们把他看成是一个公共库,以致在这个公共库上延伸出了很多Jquery插件;在项目过程中,有些插件总是不那么令人满意;
主要说两个项目用途:
1、 遮罩层,跟一般的遮罩层不一样,我需要实现的是对某一个元素进行局部遮罩;
2、 冒泡提示,网上有很多,我需要的只是一种在页面指定位置弹出来的一个静止定位的div而已;两个就自己了;
首先说下jquery插件开发的简单思路与几个操作方法
1 /*产生随机数*/ 2 ; (function ($) { 3 $.Random = function (under, over) { 4 switch (arguments.length) { 5 case 1: return parseInt(Math.random() * under + 1); 6 case 2: return parseInt(Math.random() * (over - under + 1) + under); 7 default: return 0; 8 } 9 } 10 })(jQuery);
1、 我们一般在写一个公共方法时会在前面定义一个”;”,在网上看到有人说这是书写习惯,其实这个一种容错机制,有点sql中go的意思,就是把上面的代码跟自己的这个扩展插件给分开,因为插件是被引入到页面,如果这个插件上面有错误,且别人忘记以”;”结尾,那么我们写的这个插件就会受到影响,所以加上开头的分号;
2、 分号后面在定义一个匿名的函数,并且闭包,传入一个jQuery参数进去,就完成一个jquery插件的基本定义;
1 ; (function ($) { 2 })(jQuery);
3、 插件的主体就是我们的功能处理块;一般我们使用的jquery的时候,有两种情况
a、$.ajax(option);这种是属于jquery本身提供的方法
b、$(“”).each(option);这种是属于jquery对象提供的方法
我们上面的两个需求中,第一个需求是进行局部遮罩,就需要知道是遮罩那个页面元素了,显示就需要封装一个jquery对象方法;
先完成第一个需求
1 ; (function ($) { 2 $.fn.extend({ 3 loading: function (option, param) { 4 } 5 }); 6 })(jQuery);
因为我们是封装到对象上的方法,所以使用$.fn.extend(opiotn)将我们的loading函数累加到$.fn上面,写完这些就可以在js中使用$(“#div1”).loading(option,param);进行调用了;
还有一个最重要的东西就是参数的合并;一个插件肯定是可以灵活配置的,当然我们也应该默认的提供一个看上去可以使用的页面参数;
在定义完$.fn.extend后,接着定义默认值:
1 $.fn.loading.defaults = { 2 loadingText: "loading", //加载时要显示的文字 3 loadingHide: false, //原来的遮罩层是否需要隐藏 4 opacity: 0.6, //透明度 5 bgColor: "black", //遮罩层的背景色 6 fontColor: "#fff" //遮罩层中的color 7 }
默认值有了后,我们还需要将我们自定义的参数给传进来,比如我们的调用
$("#div4").loading({ loadingText: "加载用户信息中,请稍后" });
Juery给我们提供了一个方式用来合并相关参数
option = $.extend($.fn.loading.defaults, option);
这个option就是默认参数+我们传进来的自定义参数合并后的结果了;
其实这个比较简单,就是因为我们知道要遮罩层的对象,然后进行创建一个div在需要被遮罩的div的上方就可以;
$this.position().top;可以得到传进来的div距离浏览器上方的高度
$this.position().left; 可以得到传进来的div距离浏览器左边的宽度
给遮罩层赋值,就是给offset(option)进行赋值,赋值方式,可以参考w3cschool上的示例;
$("#" + loadingId).offset(function (n, c) { newPos = new Object(); newPos.left = left; newPos.top = top; return newPos; })
基本在此就已经封装完了整个插件;现在在遮罩层的基础上在加一个需求,比如在已经弹出的遮罩层的基础上在次改变遮罩层的相关属性,比如文字,颜色等;
我们用过一些jquery组件都会提供事件和方法
比如我们在使用jquery.easyui的时候,事件在定义在option中,就可以在符合条件时触发,
我们在option里面加入一个参数就可以做到
onLoadSuccess:null //调用回调 //在符合条件的时候回调一下即可 if (option.onLoadSuccess != null) { option.onLoadSuccess("这是参数"); }
方法的写法如下:
1 loading: function (option, param) { 2 var $this = this; 3 if (typeof option == 'string') { 4 switch (option) { 5 case 'setLoadingText': 6 return this.each(function () { 7 var id = $this.attr("id"); 8 var loadingId = id + "Loading"; 9 $("#" + loadingId + "span").html(param); 10 }); 11 } 12 } 13 } 14 //调用 15 $("#div1").loading("setLoadingText","系统错误,请与管理员联系");
我刚开始以为要用到jquery里面的什么提供的方式,其实很简单,判断下option是不是string,如果是就是方法的调用;我们在使用jquery的时候,一定是要将当前对象给each一下,因为我们有可能不止选择一个对象,而是多个;
同样的冒泡提示也是如此,只是用$.tips=function(option,param)即可
下面贴出代码:
效果图:
1 ; (function ($) { 2 $.Tips = function (option, param) { 3 if (typeof(option) == 'string') { 4 switch(option){ 5 case "setText": 6 setText(param); 7 break; 8 case "setHtml": 9 setHtml(param); 10 break; 11 case "hideTips": 12 hideTips(param); 13 break; 14 case "hideTipsAll": 15 hideTipsAll(); 16 break; 17 } 18 return; 19 } 20 21 function setText(param) { 22 $("#" + param.id + "span").text(param.text); 23 } 24 function setHtml(param) { 25 $("#" + param.id + "span").html(param.html); 26 } 27 function hideTips(param) { 28 $("#" + param.id).remove(); 29 } 30 31 function hideTipsAll() { 32 $(".tips-MessTips").remove(); 33 } 34 35 var setting = { 36 id: 'auto', 37 width: 300, 38 height: 20, 39 backgroundColor: "rgb(217, 237, 247)", 40 left: 'auto', 41 top: 20, 42 tipMess: '正在加载中,请稍后', 43 iconImg: '../Content/MyCustom/images/info16.png', 44 time: 3 45 } 46 47 var documentWidth = $(document).width(); 48 var documentHeight = $(document).height(); 49 //合并参数 50 option = $.extend(setting, option); 51 52 if (option.left = 'auto') { 53 var tipWidth = option.width; 54 if (typeof (option.width) == "string") { 55 tipWidth = documentWidth *parseFloat(option.width)/100; 56 } 57 option.left = (documentWidth - tipWidth) / 2; 58 } else if (typeof (option.left) == "string" && option.left.indexOf("%") > 0) { 59 option.left = option.left * parseFloat(option.left) / 100; 60 } 61 if (typeof (option.top) == "string" && option.top.indexOf("%") > 0) { 62 option.top = option.top * parseFloat(option.top) / 100; 63 } 64 if (typeof (option.width) == "string" && option.width.indexOf("%") > 0) { 65 option.width = documentWidth * parseFloat(option.width) / 100; 66 } 67 if (typeof (option.height) == "string" && option.height.indexOf("%") > 0) { 68 option.height = documentHeight * parseFloat(option.height) / 100; 69 } 70 71 if (option.id == 'auto') { 72 option.id = 'tips' + $.Random(0, 999999999); 73 } 74 75 option.time = option.time * 1000; 76 77 78 var htmlDiv = '<div class=" tips-MessTips" id="' + option.id 79 + '" ' 80 + 'style="display:none;overflow:hidden;border-color:#bce8f1;color:#3a87ad;left: ' + option.left 81 + 'px; top: ' + option.top 82 + 'px; width: ' + option.width 83 + 'px; height: ' + option.height 84 + 'px; position: fixed; background-color:' + option.backgroundColor 85 + ';"><img src="' + option.iconImg 86 + '" /><span id="' + option.id 87 + 'span">' + option.tipMess 88 + '</span></div>' 89 90 //如果已经存在的DIV 91 if ($("#" + option.id).length > 0) { 92 $("#" + option.id + "span").html(option.tipMess); 93 return; 94 } 95 96 //如果还没有一个tip 97 if ($(".tips-MessTips").length == 0) { 98 $("body").append(htmlDiv); 99 } else { 100 $(".tips-MessTips").each(function () { 101 $(this).offset(function (n, c) { 102 newPos = new Object(); 103 newPos.left = $(this).offset().left; 104 newPos.top = $(this).offset().top + option.height; 105 return newPos; 106 }) 107 }); 108 109 $(".tips-MessTips").first().before(htmlDiv); 110 } 111 if (option.time > 0) { 112 setTimeout( 113 function () { 114 $("#" + option.id).remove(); 115 }, option.time); 116 } 117 118 //显示当前的窗口 119 return $("#" + option.id).show(); 120 }; 121 122 })(jQuery);
效果图
会在指定时间消失,多个自动往下排列