[转载]ASP.NET MVC3的伪静态实现

mikel阅读(995)

[转载]ASP.NET MVC3的伪静态实现 – wxj200589 – 博客园.

最近使用ASP.NET MVC3开发B2C电子商务系统,为了SEO的优化工作,需要通过路由实现伪静态URL,后续再根据需要生成真正的静态页面,不直接走路由访问具体的页面。现在开始研究第一步,如何定义自己的路由规则,达到伪静态的功能需求。

基本实现原理如下图:

首先,关于命名空间。

路由的功能是为了让所有ASP.NET网站开发都可以使用,所以dll并没有在MVC中,而是在System.Web中的System.web.Routing。

现在我们为了我们实际的需求,实现MVC3中的自定义路由功能(继承RouteBase,重写RouteData和VirtualPathData)。

下面的例子实现以下目的:输入一个youdomin.com/product/123.html,执行TestController中Index.

第一步:实现TestRoute

1 RouteData 每次访问URL都会从此入口

通过httpContext.Request.AppRelativeCurrentExecutionFilePath 获取我们访问的url地址,根据地址进行分析:是不是符合我们的规则,符合我们规则我们就走特定的Controller和Action。代码如下:

public class TestRoute:RouteBase
{
private string[] urls;
public TestRoute(params string[]targetUrls) {
urls = targetUrls;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string requestedURL =
httpContext.Request.AppRelativeCurrentExecutionFilePath+httpContext.Request.PathInfo;
requestedURL = requestedURL.Substring(2).Trim(/);

if (requestedURL.Contains(urls.ToArray().GetValue(0).ToString()))
{
result = new RouteData(thisnew MvcRouteHandler());
result.Values.Add(controllerTest);
result.Values.Add(actionIndex);
result.Values.Add(p, requestedURL);
}
return result;
}

public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}

}

上面例子中,我们根据判断Url中是否符合某个特定的值来特定执行特定Controller和特定Action,没有就返回null。

第二步,在Global.aspx中注册我们自己的路与规则:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute({resource}.axd/{*pathInfo});

routes.Add(new TestRoute(product));
routes.MapRoute(
Default// Route name
{controller}/{action}/{id}// URL with parameters
new { controller = Home, action = Index, id = UrlParameter.Optional } // Parameter defaults
);

}

protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();

RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}

注意上述代码红色部分,Application_Start() 中注册一个路由规则,RegisterRoutes(RouteTable.Routes)然后在RegisterRoutes方法中加入如下代码:

routes.Add(new TestRoute(“product”));

注明:TestRoute 是上面我们自己定义的路由,实现RouteBase的类。

第三步:新建第一步中测试用的Controller

public class TestController:Controller
{
public ActionResult Index(string p)
{
ViewData[t] =p;
return View(“”);
}
}

第四步:新建一个视图

@{
Layout = null;
}

<!DOCTYPE html>

<html>
<head>
<title></title>
</head>
<body>
<div>
<!–此处会显示你输入的URL地址–>
@ViewData[t].ToString()
</div>
</body>
</html>

第五步,直接输入URl测试

比如:http://127.0.0.1/product/1.html

后续补充:

主要内容:如何让前台列表展示页显示以上5步中实现的伪静态URL?

经过实际验证,发现RouteBase中VirtualPathData的实现就能解决以上问题。.net Route其实已经实现了这个双向解析的问题,通过输入URL,从RouteData进入,根据自己的路由规则进行解析到相对应的Controller和 Action,然后在使用URL.Action的地方从VirtualPathData解析出符合路由规则的URL地址,具体代码如下:

public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
if (values["controller"].ToString().Contains("Test"))
{
return new VirtualPathData(this, "product/" + values["p"] + ".html");
}
else
return null;
}

可以替换第一步中TestRoute类中的GetVirtualPath方法,查看实际效果。

[转载]菜鸟的Android 学习笔记 之四大组件总结

mikel阅读(1001)

[转载]菜鸟的Android 学习笔记 之四大组件总结 – 行者,无疆 – 博客园.

Activity :

activity是Android提供的界面,所有和用户交互的事情都发生在这个类。

Activity的生命周期:

OnCreate() 创建的时候会调用这个方法 设置这个Activity设置布局控件,初始化数据等。

OnStart() 当控件被我们看到的时候,就调用OnStart()方法 在OnCreate()之后 或者OnStop()后调用。

OnResume() 当重新获得用户焦点的时候就调用这个方法。

onPause() 保护现场用的 可能被另外一个透明的Dialog窗口覆盖,失去焦点,但是他仍然和窗口管理器保持连接,系统可以继续保护Activity的内部状态。

onStop()  停止和OnStart()对应,注意OnStop()和OnPause()不一样的地方是他是完全被另外一个窗口覆灭。也就是失去焦点而且不可见。

onDestory()销毁

创建一个Activity的要点

1.一个Activity就是一个类,并且这个类要继承Activity

2.需要复写onCreate方法

3.要在Androidmanifest.xml进行配置(四大组件都要进行配置)

4.为Activity添加必要的控件

在一个Activity启动另外一个Activity

Intent intent=new Intent(CurrentActivity.this,OtherActivity.class) //OtherActivity也要在AndroidManifest.xml中注册。

startActivity(intent);

Intent还可以放入各种数据 传入下一个Activity。

可以用下面的方式接收传过来的数据:

还可以用Bundel的方法发送数据。Bundel就像一个承载数据的List<> 里面可以放各种数据。但是他的key只能是string类型的,放入Intent的方式也是intent.putExtra(bundle);

Service

Service在什么时候会用到呢?比如说音乐程序的时候,需要后台运行,但是又看不到界面,此时就用到Service。这也是Service和Activity最大的不同。

Service 分类:

本地Service和远程Service。

本地Service是同一个进程内Service彼此之间共同的内存区域。

远程Service是同一个系统内的不同进程之间访问(注意是同一个系统内,不是不同系统)。

Service生命周期:

onCreate()->onStart()->onDestory() 三个方法 继承的时候如果要用到记得重新写下。

创建一个Service要点:

1.  创建一个类,要继承Service

2.  要在androidManifest.xml中进行配置

3.  在Activity中配置调用它

Broadcast Receiver

在android中可以通过broadcast告诉其他程序发生了什么事情。比如电源,比如短信,比如信号。

Broadcast Receiver相比于其他组件比较特别的地方是他还提供了一种所谓的热注册,就是不用写在androidManifest.xml中,直接用

registerReceiver(Broadcastreceiver,IntentFilter)注册。然后用unregisterReceiver()消除注册。

一些常用的Action:

ACTION_CALL                                              activity                        启动一个电话.
ACTION_EDIT                                               activity                       显示用户编辑的数据.
ACTION_MAIN                                               activity                      作为Task中第一个

Activity启动
ACTION_SYNC                                             activity                         同步手机与数据服务器上的数据.
ACTION_BATTERY_LOW                           broadcast receiver       电池电量过低警告.
ACTION_HEADSET_PLUG                        broadcast receiver        插拔耳机警告
ACTION_SCREEN_ON                               broadcast receiver        屏幕变亮警告.
ACTION_TIMEZONE_CHANGED              broadcast receiver         改变时区警告.

Content Provider

这个组件比较复杂

ContentProvider可以支持多个应用的数据共享。可以用来操作音频,视频,图片,私人通讯录等等。但是要记得获取适当的读取权限。当然也可以用来公开自己的数据。(难道Content Provider是全局变量?整个手机的程序都能访问得到?)

创建Content Provider 要点:

1.  创建一个Content Provider首先要继承ContentProvider类。

2.       在androidManifest中配置

3.       在Activity类中调用insert() query()可以调用

实际上所有的contentprovider用户都不能直接访问contentprovider实例,只能通过ContentResolver的中 间代理。而要得到ContentResolver的实例可以用Activity的getContentResolver方法。如下

可以自己去调用ContentProvider相应的方法。

Uri使用方法:来自http://blog.sina.com.cn/s/blog_5688414b0100xagp.html

一、Content Provider基本概念

1、ContentProvider为存储和获取数据提供了统一的接口。ContentProvide对数据进行封装,不用关心数据存储的细节。使用表的形式来组织数据。

Android学习十九:ContentProvider初步

2、使用ContentProvider可以在不同的应用程序之间共享数据。

3、Android为常见的一些数据提供了默认的ContentProvider(包括音频、视频、图片和通讯录等)。

ContentProvider所提供的函数:

query(),insert(),update(),delete(),getType(),onCreate()等。

二、URI统一资源标识符)的使用方法

为系统的每一个资源给其一个名字,比方说通话记录。

1、每一个ContentProvider都拥有一个公共的URI,这个URI用于表示这个ContentProvider所提供的数据。

2、Android所提供的ContentProvider都存放在android.provider包中。 将其分为A,B,C,D 4个部分:

Android学习十九:ContentProvider初步

A:标准前缀,用来说明一个Content Provider控制这些数据,无法改变的;”content://”

B:URI 的标识,它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的 类名。这个标识在 元素的 authorities属性中说明:一般是定义该ContentProvider的包.类的名称;”content://hx.android.text.myprovider”

C:路径,不知道是不是路径,通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义,记得在使用的时候保持一致就ok了;”content://hx.android.text.myprovider/tablename”

D:如果URI中包含表示需要获取的记录的ID;则就返回该id对应的数据,如果没有ID,就表示返回全部; “content://hx.android.text.myprovider/tablename/#” #表示数据id

三、ContentProvider的实现过程

自己实现ContentProvider不常见,因为可能不需要和别的应用程序交换数据。使用内置的ContentProvider比较多。

1、定义一个CONTENT_URI常量,提供了访问ContentProvider的标识符。

public static final Uri CONTENT_URI = Uri.parse("content://com.example.codelab.transportationprovider");

其中:content是协议

Com.exmaple.codelab.transportationprovider是类名,包含完整的包名。

Uri.parse将一个字符串转换成Uri类型。

如果Provider包含子表,同样定义包含字表的CONTENT_URI。

content://com.example.codelab.transportationprovider/train
content://com.example.codelab.transportationprovider/air/domestic
content://com.example.codelab.transportationprovider/air/international

然后定义列,确保里面包含一个_id的列。

2、定义一个类,继承ContentProvider。

public class FirstContentProvider extends ContentProvider

先介绍一下ContentProvider用到的UriMatcher。UriMatcher的一个重要的函数是match(Uri uri)。这个函数可以匹配Uri,根据传入的不同Uri返回不同的自定义整形值,以表明Uri访问的不同资源的类型。

例如:

      public static final UriMatcher uriMatcher;
      static {
                     uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
                     uriMatcher.addURI(Book.AUTHORITY, "item", Book.ITEM);
                     uriMatcher.addURI(Book.AUTHORITY, "item/#", Book.ITEM_ID);
              }

这里UriMatcher类型的静态字段是用来匹配传入到ContentProvider中的Uri的类。其构造方法传入的匹配码是使用match()方法匹配根路径时返回的值,这个匹配码可以为一个大于零的数表示匹配根路径或传入-1,即常量UriMatcher.NO_MATCH表示不匹配根路径。 addURI()方法是用来增加其他URI匹配路径的,第一个参数传入标识ContentProvider的AUTHORITY字符串。第二个参数传入需要匹配的路径,这里的#号为通配符,代表匹配任意数字,另外还可以用*来匹配任意文本。第三个参数必须传入一个大于零的匹配码,用于match()方法对相匹配的URI返回相对应的匹配码。 例如:sMatcher.addURI(“com.test.provider.personprovider”, “person”, 1);如果match()方法匹配content://com.test.provider.personprovider/person路径,返回匹配码为1。

3、实现query,insert,update,delete,getType和onCreate方法。

4、在AndroidManifest.xml当中进行声明。

<!-- android:name是完成ContentProvider类的全称
             android:authorities是和FirstProvidermetaData中的常量AUTHORITY的值一样,否则会报错
         -->
        <provider android:name="com.bj.FirstContentProvider"
            android:authorities="com.bj.firstcontentprovider"
            />

四、具体代码

Activity19Activity.java

public class Activity19Activity extends Activity {

private Button queryButton = null;

private Button insertButton = null;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

queryButton = (Button) this.findViewById(R.id.query);

queryButton.setOnClickListener(newQueryListener());

insertButton = (Button) this.findViewById(R.id.insert);

insertButton.setOnClickListener(newInsertListener());

System.out.println(getContentResolver().getType(FirstProvidermetaData.UserTableMetaData.CONTENT_URI));

}

class InsertListener implementsOnClickListener {

@Override

public void onClick(View v) {

// TODOAuto-generated method stub

ContentValues values = new ContentValues();

values.put(FirstProvidermetaData.UserTableMetaData.USER_NAME,

“michal”);

Uri uri = getContentResolver()

.insert(

FirstProvidermetaData.UserTableMetaData.CONTENT_URI,

values);

System.out.println(“uri—>” + uri.toString());

}

}

class QueryListener implementsOnClickListener {

public void onClick(View v) {

Cursor c = getContentResolver().query(

FirstProvidermetaData.UserTableMetaData.CONTENT_URI, null,

null, null, null);

while (c.moveToNext()) {

System.out.println(c.getString(c.getColumnIndex(“username”)));

}

}

}

}

FirstContentProvider.java

public class FirstContentProvider extendsContentProvider {

// 当别的程序来访问这个ContentProvider,是通过Uri来访问的,UriMatcher检查是否符合标准

// uri起一个规则,返回数字

public static final UriMatcher uriMatcher;

// 下面定义两个规则

public static final int INCOMING_USER_COLLECTION = 1;

public static final int INCOMING_USER_SINGLE = 2;

private DatabaseHelper dh;

static {//下面的users前面不能加/

uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

uriMatcher.addURI(FirstProvidermetaData.AUTHORITY, “users”,

INCOMING_USER_COLLECTION);

uriMatcher.addURI(FirstProvidermetaData.AUTHORITY, “users/#”,

INCOMING_USER_SINGLE);

}

//有点类似于SQL里面表的别名,这个也是给列其别名,必须要用

//列的别名还是原来的名,没必要修改

public static HashMap<String,String> userProjectionMap;

static{

userProjectionMap = newHashMap<String,String>();

userProjectionMap.put(UserTableMetaData._ID, UserTableMetaData._ID );

userProjectionMap.put(UserTableMetaData.USER_NAME, UserTableMetaData.USER_NAME);

}

@Override

public int delete(Uri uri, String selection, String[] selectionArgs) {

// TODOAuto-generated method stub

System.out.println(“delete”);

return 0;

}

// 根据传入的URI,返回该URI所表示的数据类型

// 也就是说,我们通过URI要访问的数据,返回什么类型

@Override

public String getType(Uri uri) {

// TODOAuto-generated method stub

System.out.println(“getType”);

switch (uriMatcher.match(uri)) {

case INCOMING_USER_COLLECTION:

// UserTableMetaDataFirstProvidermetaData的内部类

return UserTableMetaData.CONTENT_TYPE;

case INCOMING_USER_SINGLE:

return UserTableMetaData.CONTENT_TYPE_ITEM;

default:

throw new IllegalArgumentException(“Unknown uri” + uri);

}

}

@Override

public Uri insert(Uri uri, ContentValues values) {

// TODOAuto-generated method stub

System.out.println(“insert”);

SQLiteDatabase db = dh.getWritableDatabase();

//返回表中自动增长的列的值,否则返回-1

long rowId = db.insert(UserTableMetaData.TABLE_NAME,null, values);

if(rowId>0){

//rowId追加到后面

//contentUris:用来处理Uri的工具类

Uri insertedUserUri = ContentUris.withAppendedId(UserTableMetaData.CONTENT_URI, rowId);

//通知监听器,数据已经改变

getContext().getContentResolver().notifyChange(insertedUserUri, null);

return insertedUserUri;

}

throw new SQLException(“Failed to insert row into “+uri);

}

//是一个回调方法,所以说在ContentProvider创建的时候执行

//也就是创建这个DatabaseHelper对象

@Override

public boolean onCreate() {

// TODOAuto-generated method stub

//getContext得到当前正在运行着的context

dh = new DatabaseHelper(getContext(),FirstProvidermetaData.DATABASE_NAME);

System.out.println(“on create”);

SQLiteDatabase db = dh.getReadableDatabase();

return true;

}

//projection:查询的列有哪些

//selection:where子句的内容,可以用?

//selectionArgs:占位符对应的参数

//sortOrder:排序

@Override

public Cursor query(Uri uri, String[] projection, String selection,

String[] selectionArgs, String sortOrder) {

// TODOAuto-generated method stub

System.out.println(“query”);

//创建一个查询的语句

SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

switch(uriMatcher.match(uri)){

case INCOMING_USER_COLLECTION:

//设置查询哪张表

qb.setTables(UserTableMetaData.TABLE_NAME);

qb.setProjectionMap(userProjectionMap);

break;

case INCOMING_USER_SINGLE:

qb.setTables(UserTableMetaData.TABLE_NAME);

qb.setProjectionMap(userProjectionMap);

//添加where条件,getPathSegments:得到uripath部分content:XXX/user/1get(1)得到1

qb.appendWhere(UserTableMetaData._ID+”=”+uri.getPathSegments().get(1));

break;

}

String orderBy;

if(TextUtils.isEmpty(sortOrder)){

orderBy = UserTableMetaData.DEFAULT_SORT_ORDER;

}

else

{

orderBy = sortOrder;

}

SQLiteDatabase db = dh.getWritableDatabase();

//下面的query使用qb这个对象

Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);

//也是通知下

c.setNotificationUri(getContext().getContentResolver(), uri);

System.out.println(“query”);

return c;

}

@Override

public int update(Uri uri, ContentValues values, String selection,

String[] selectionArgs) {

// TODOAuto-generated method stub

return 0;

}

}

FirstProvidermetaData.java

public class FirstProvidermetaData {

public static final String AUTHORITY=”com.bj.firstcontentprovider”; //继承了contentprovider的类的全名

//数据库名称

public static final String DATABASE_NAME = “FirstProvider.db”;

//数据库的版本

public static final int DATABASE_VERSION = 1;

//表名

public static final String USERS_TABLE_NAME = “users”;

public static final class UserTableMetaData implements BaseColumns{

//表名

public static final String TABLE_NAME=”users”;

//访问该ContentProviderURI

public static final Uri CONTENT_URI=Uri.parse(“content://”+AUTHORITY+”/users”);

public static final String CONTENT_TYPE=”vnd.android.cursor.dir/vnd.firstprovider.user”;

public static final String CONTENT_TYPE_ITEM=”vnd.android.cursor.item/vnd.firstprovider.user”;

//列名,在users表中添加一个名为name的列

public static final String USER_NAME=”name”;

//默认排序方式

public static final String DEFAULT_SORT_ORDER=”_id desc”;

}

}

DatabaseHelper.java见Android学习十六:SQLite使用方法

[转载]Create subgrid with master datagrid - jQuery EasyUI

mikel阅读(1450)

[转载]Create subgrid with master datagrid – jQuery EasyUI.

Use the detail view of datagrid, users can expand a row to show additional details. As any content can be loaded as the row details, the subgrid can be dynamically loaded also. This tutorial will show you how to create a subgrid with master datagrid.

Step 1: Create Master DataGrid

  1. <table id=“dg” style=“width:700px;height:250px” url=“datagrid22_getdata.php” title=“DataGrid – SubGrid” singleselect=“true” fitcolumns=“true”>
  2. <thead>
  3. <tr>
  4. <th field=“itemid” width=“80”>Item ID</th>
  5. <th field=“productid” width=“100”>Product ID</th>
  6. <th field=“listprice” align=“right” width=“80”>List Price</th>
  7. <th field=“unitcost” align=“right” width=“80”>Unit Cost</th>
  8. <th field=“attr1” width=“220”>Attribute</th>
  9. <th field=“status” align=“center” width=“60”>Status</th>
  10. </tr>
  11. </thead>
  12. </table>

Step 2: Set Detail View to show subgrid

To use the detail view, remember to include the view script file to your page header.

  1. <script type=“text/JavaScript src=“http://www.jeasyui.com/easyui/datagrid-detailview.js”></script>
  1. $(‘#dg’).datagrid({
  2. view: detailview,
  3. detailFormatter:function(index,row){
  4. return ‘<div style=”padding:2px”><table id=”ddv-‘ + index + ‘”></table></div>’;
  5. },
  6. onExpandRow: function(index,row){
  7. $(‘#ddv-‘+index).datagrid({
  8. url:‘datagrid22_getdetail.php?itemid=’+row.itemid,
  9. fitColumns:true,
  10. singleSelect:true,
  11. rownumbers:true,
  12. loadMsg:,
  13. height:‘auto’,
  14. columns:[[
  15. {field:‘orderid’,title:‘Order ID’,width:100},
  16. {field:‘quantity’,title:‘Quantity’,width:100},
  17. {field:‘unitprice’,title:‘Unit Price’,width:100}
  18. ]],
  19. onResize:function(){
  20. $(‘#dg’).datagrid(‘fixDetailRowHeight’,index);
  21. },
  22. onLoadSuccess:function(){
  23. setTimeout(function(){
  24. $(‘#dg’).datagrid(‘fixDetailRowHeight’,index);
  25. },0);
  26. }
  27. });
  28. $(‘#dg’).datagrid(‘fixDetailRowHeight’,index);
  29. }
  30. });

When users click expand button(‘+’), the ‘onExpandRow’ event will be triggered. We create a new subgrid with tree columns. Remember to call ‘fixDetailRowHeight’ method for master datagrid when subgrid load data successfully or resize.

Step 3: The Server Code

datagrid22_getdata.php
  1. $result = array();
  2. include ‘conn.php’;
  3. $rs = mySQL_query(“select * from item where itemid in (select itemid from lineitem)”);
  4. $items = array();
  5. while($row = mySQL_fetch_object($rs)){
  6. array_push($items, $row);
  7. }
  8. echo json_encode($items);
datagrid22_getdetail.php
  1. $itemid = $_REQUEST[‘itemid’];
  2. include ‘conn.php’;
  3. $rs = mysql_query(“select * from lineitem where itemid=‘$itemid’“);
  4. $items = array();
  5. while($row = mysql_fetch_object($rs)){
  6. array_push($items, $row);
  7. }
  8. echo json_encode($items);

Download the EasyUI example:

[转载]使用 jQuery Uploader 显示文件上传进度

mikel阅读(1093)

[转载]使用 jQuery Uploader 显示文件上传进度 – 麦丝平方 – 博客园.

上传大文件的时候, 我们都希望可以向用户显示上传的进度, 那么这里介绍的就是使用 JQuery Uploader 来实现这样的效果.

请到 Download 下载资源JQueryElement 示例下载一节下载示例代码

本文将说明 Uploader 控件的功能以及使用过程中的注意事项和技巧, 目录如下:

* 准备

* 创建保存页面

* 添加 FileUpload 控件

* 设置 EnableSessionState

* 调用 Uploader 的 Save 方法

* 创建获取进度的页面

* 创建上传页面

* 设置保存页面

* 设置获取进度的页面

* 上传

* 隐藏保存页面

准备

请确保已经在 Download 下载资源 中的 jQueryElement.dll 下载一节下载 jQueryElement 最新的版本.

请使用指令引用如下的命名空间:

<%@ Register Assembly="zoyobar.shared.panzer.JQueryElement"
    Namespace="zoyobar.shared.panzer.ui.jqueryui.plusin"
    TagPrefix="je" %>
<%@ Register Assembly="zoyobar.shared.panzer.JQueryElement"
    Namespace="zoyobar.shared.panzer.web.jqueryui"
    TagPrefix="je" %>

除了命名空间, 还需要引用 jQueryUI 的脚本和样式, 在 Download 下载资源JQueryElement.dll 下载一节下载的压缩包中包含了一个自定义样式的 jQueryUI, 如果需要更多样式, 可以在 http://jqueryui.com/download 下载:除了命名空间, 还需要引用 jQueryUI 的脚本:

<link type="text/css" rel="stylesheet" href="[样式路径]/jquery-ui-<version>.custom.css" />
<script type="text/javascript" src="[脚本路径]/jquery-<version>.min.js"></script>
<script type="text/javascript" src="[脚本路径]/jquery-ui-<version>.custom.min.js"></script>

创建保存页面

保存页面是一个简单的页面, 主要完成文件的保存工作, 保存页面不会自己提交, 而是由上传页面提交.

添加 FileUpload 控件

首先, 为保存页面添加 FileUpload 控件:

<form id="formFileUpload" runat="server">
上传:&nbsp;<asp:FileUpload ID="file" runat="server" />
</form>

也可以添加 type 属性为 file 的 input 元素:

<form id="formFileUpload" runat="server" enctype="multipart/form-data">
上传:&nbsp;<input type="file" id="file" runat="server" />
</form>

如果使用 input 元素, 则可能需要设置 form 的 enctype 属性为 multipart/form-data.

设置 EnableSessionState

需要设置保存页面的 EnableSessionState 为 ReadOnly, 这样可以在保存页面保存文件时, 请求获取进度的页面. 这主要是由于, ASP.NET 顺序执行可以读写 Session 的页面:

<%@ Page Language="C#" AutoEventWireup="true"
    CodeFile="FileUpload.aspx.cs" Inherits="uploader_FileUpload"
    EnableSessionState="ReadOnly" %>

调用 Uploader 的 Save 方法

在保存页面的 Page_Load 方法中, 调用 Uploader 控件的 Save静态方法来保存文件:

protected void Page_Load ( object sender, EventArgs e )
{

    if ( this.IsPostBack && this.file.HasFile )
        // TODO: 这里设置 waitSecond 参数是为了在测试时显示上传的进度,
        // 在实际的使用中请不要设置 waitSecond, 并调整 bufferSize 为一个合理的值.
        Uploader.Save (
            this.Server.MapPath ( @"~/uploader/photo.jpg" ),
            this.file.PostedFile,
            this.Session["myphotouploadinfo"] as Uploader.UploadInfo,
            1,
            1 );
        //    Uploader.Save (
        //        this.Server.MapPath ( @"~/uploader/photo.jpg" ),
        //        this.file.PostedFile,
        //        this.Session["myphotouploadinfo"] as Uploader.UploadInfo );

}

代码中, 通过 IsPostBackHasFile 属性判断在用户提交了文件后, 才进行保存.

Save 方法的格式为 Save ( string filePath, HttpPostedFile postedFile, Uploader.UploadInfo uploadInfo, int bufferSize, int waitSecond ), filePath 参数是文件保存的完整路径, postedFile 参数是提供文件控制的 HttpPostedFile 对象, 可以从 FileUpload 控件获取, uploadInfo 参数是保存上传进度信息的对象, bufferSize 参数是保存文件时的缓存大小, 默认 128kb 保存一次, 最后一个参数 waitSecond 只在测试时使用, 表示缓存被保存后的等待时间, 这样可以确保看到进度.

上传大于 4mb 的文件, 请修改 web.config 的 maxRequestLength, 可以参考 http://msdn.microsoft.com/zh-cn/library/e1f13641(v=vs.71).aspx.

创建获取进度的页面

包含进度信息的对象 Uploader.UploadInfo 被保存在 Session 中, 因此可以随时从 Session中获取进度信息:

<%@ WebHandler Language="C#" Class="uploader_getprec" %>

using System.Collections.Generic;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.SessionState;
using zoyobar.shared.panzer.ui.jqueryui.plusin;

public class uploader_getprec : IHttpHandler,
    IReadOnlySessionState
{

    public void ProcessRequest ( HttpContext context )
    {
        context.Response.ContentType = "text/javascript";
        context.Response.Cache.SetNoStore ( );

        Uploader.UploadInfo info =
            context.Session["myphotouploadinfo"] as Uploader.UploadInfo;

        SortedDictionary<string, object> json =
            new SortedDictionary<string, object> ( );

        if ( null == info )
            json.Add ( "prec", "-" );
        else
        {
            json.Add ( "prec", info.Percent );
            json.Add ( "total", info.ContentLength );
            json.Add ( "posted", info.PostedLength );

            if ( info.Percent == 100 )
                json.Add ( "url", "photo.jpg" );

            /*
             * {
             *    "prec": 20.23
             *    "total": 2000,
             *    "posted": 2
             * }
             * */
        }

        context.Response.Write (
            new JavaScriptSerializer ( ).Serialize ( json )
            );
    }

    ...

}

需要注意的是, uploader_getprec 实现了接口 IReadOnlySessionState, 而不是 IRequiresSessionState. 原因也是和设置 EnableSessionState 为 ReadOnly 类似的. 至于如何返回 JSON 数据, 请参考 在不同的 .NET 版本中返回 JSON.

创建上传页面

最后一步就是创建一个上传页面, 在页面中添加 Uploader控件:

<je:Uploader ID="uploader" runat="server" IsVariable="true"
    UploadUrl="FileUpload.aspx"
    ProgressUrl="getprec.ashx" ProgressChanged="
function(data){

    if(-:data.prec == '-')
        $('#prec').text('没有进度!');
    else
        if(-:data.prec == 100){
            $('#prec').text('完成, 图片路径为: ' + -:data.url);

            pb.hide();

            $('#photo').show().attr('src', -:data.url);
        }
        else{
            $('#prec').text(-:data.posted +
                ' bytes/' + -:data.total + ' bytes, ' +
                -:data.prec + '%');
            pb.progressbar('option', 'value', -:data.prec);
        }

}
">
</je:Uploader>
<je:Button ID="cmdUpload" runat="server" IsVariable="true"
    Label="开始" Click="
function(){
    cmdUpload.hide();
    uploader.__uploader('hide').__uploader('upload');

    pb.show();
}">
</je:Button>

设置保存页面

通过 UploaderUploadUrl 属性, 可以选择保存页面, 示例中, 选择了页面 FileUpload.aspx, 这将自动生成一个 iframe 元素, iframe 的 src 属性指向 FileUpload.aspx 页面.

也可以自定义一个 iframe, 然后通过 Upload属性选择此 iframe:

<iframe id="myIFrame" src="FileUpload.aspx"></iframe>

<je:Uploader ID="uploader" runat="server" IsVariable="true"
    Upload="#myIFrame"
    ...    >
</je:Uploader>

设置获取进度的页面

通过属性 ProgressUrlProgressChanged 可以获取并显示上传进度, ProgressUrl 表示返回进度信息的页面地址, ProgressChanged 则用于处理返回的进度等信息.

此外 ProgressInterval 属性表示查询进度的时间间隔, 默认为 1000 毫秒.

上传

调用 uploaderupload方法, 即可触发上传操作:

<je:Button ID="cmdUpload" runat="server" IsVariable="true"
    Label="开始" Click="
function(){
    uploader.__uploader('upload');
}">
</je:Button>

默认情况下将对保存页面的第一个表单执行 submit 操作, 可以通过 UploadForm 属性来调整需要提交的表单的索引.

隐藏保存页面

调用 uploaderhide方法, 即可隐藏保存页面:

...
    uploader.__uploader('upload');
...

JQueryElement 是开源共享的代码, 可以在 http://code.google.com/p/zsharedcode/wiki/Download 页面下载 dll 或者是源代码.

实际过程演示: http://www.tudou.com/programs/view/-Zvwz5xsih8/, 建议全屏观看.

欢 迎访问 panzer 开源项目, http://zsharedcode.googlecode.com/ , 其中包含了 IEBrowser 控制 WebBrowser 执行各种 js 和 jQuery 脚本以及录制功能 和 jQueryUI 的 ASP.NET 控件 JQueryElement, 微博: http://t.qq.com/zoyobar

[转载]分享35款超酷的免费英文涂鸦字体

mikel阅读(1478)

[转载]分享35款超酷的免费英文涂鸦字体 – 梦想天空(山边小溪) – 博客园.

在街头艺术中,涂鸦是艺术家自我表达的常用形式。如今,在专业设计师的帮助下,我们也能够把涂鸦字体应用到我们的设计中,让我们的作品脱颖而 出。今天这篇文章就向大家分享35款超酷的免费英文涂鸦字体,如果您正在寻找漂亮的免费英文涂鸦字体,那么这份清单正是为您准备的,赶紧下载吧。

Malice

Free-Graffiti-Fonts-27

Urban Jungle

Free-Graffiti-Fonts-23

Sick Capital Vice

Free-Graffiti-Fonts-28

a dripping marker font

Free-Graffiti-Fonts-29

El&Font Urban CalIigraphy

Free-Graffiti-Fonts-24

DJ GROSS

Free-Graffiti-Fonts-25

Inner City

Free-Graffiti-Fonts-26

Ruthless One

Free-Graffiti-Fonts-01

Graffogie

Free-Graffiti-Fonts-02

ReskaGraf

Free-Graffiti-Fonts-03

Most Wasted

Free-Graffiti-Fonts-04

Subway

Free-Graffiti-Fonts-05

Sadoc Wild

Free-Graffiti-Fonts-06

QUB font

Free-Graffiti-Fonts-07

Servin’ For Salute

Free-Graffiti-Fonts-08

The Battle Continuez

Free-Graffiti-Fonts-09

JustFist

Free-Graffiti-Fonts-10

Bring Tha Noize

Free-Graffiti-Fonts-11

Writers

Free-Graffiti-Fonts-12

Aaaiight!

Free-Graffiti-Fonts-13

Broken Records (Font)

Free-Graffiti-Fonts-14

FAVELA free font

Free-Graffiti-Fonts-15

Antechamber (Free Typeface)

Free-Graffiti-Fonts-16

Laks Oner

Free-Graffiti-Fonts-17

Free Fonts

Free-Graffiti-Fonts-18

Brock Vandalo

Free-Graffiti-Fonts-19

Ruthless Two

Free-Graffiti-Fonts-20

blob – Free font by Superfried

Free-Graffiti-Fonts-21

梦想天空博客关注前端开发,展示最新HTML5CSS3应用,推荐优秀网页设计案例。

Progressive Fonts (free!)

Free-Graffiti-Fonts-22

Whoa!

Free-Graffiti-Fonts-31

Searfont

Free-Graffiti-Fonts-32

Street Soul

Free-Graffiti-Fonts-33

Souper 3

Free-Graffiti-Fonts-34

Scrawl 3rd

Free-Graffiti-Fonts-35

Let Me Ride font

Free-Graffiti-Fonts-30

[转载]五段实用的JavaScript淫荡技巧

mikel阅读(1031)

[转载]五段实用的js淫荡技巧 – !nothing – 博客园.

技巧一之setTimeout.


应用案例:比如你想一个函数循环执行10次,怎么办?以前通常是先setInterval,然后clearInterval,技巧一就是克服这个问题

(function () {
    var i = 0;
    function job() {
        console.log(i++);
        if (i < 10) {
            setTimeout(job, 1000);
        }
    }
    job();
    
})();

上面这个job函数就只会乖乖的执行10次.然后自动停止

技巧二之高效的for循环

应用案例:抛弃传统的循环方式

(function () {
   var arr=[];
   for(var i=arr.length;i--;){
      doStuff();
   }
})();

这个方式为什么高效?

一:少了一个参数l=arr.length;

二:for语句中间那个玩意少进行了一次计算,以前的话是for(i=0;i<l;i++)这样的话中间的语句会先比较i<l  然后比较出来的结果在

跟true 或者false比较,自然多了次计算

技巧三之高效赋值

应用案例:抛弃传统的if判断赋值

 var i=1,ret;
  ret=i!==1||true;
  console.log(ret);

以上代码会很神奇的告诉你ret会是true.高效吧不用if(i!==1)了在赋值了

技巧四之强悍的简短的attr

应用案例:setAttribute,getAttribute.这个方法不仅可以设置标准的属性,还可以设置任意属性,兼容好

function attr(elem, name, value) {
    var ret;
    if (value) {
        if (/msie [6-7]\.0/i.test(navigator.userAgent)) {
            ret = elem.getAttributeNode(name);
            if (!ret) {   //ie6 7不合法的属性设置捕鸟,通过这里可以设置
                ret = document.createAttribute(name);
                elem.setAttributeNode(ret);
            }
            ret.nodeValue = value + "";
        } else {
            elem.setAttribute(name, value);
        }
        return elem;
    } else {  //ie6 7有得属性获取不鸟
        ret = elem.getAttribute(name);
        fixIe = elem.getAttributeNode(name).nodeValue;
        ret = ret ? ret : fixIe ? fixIe : undefined;
        return ret;
    }
}

以上方法如何测试呢?

attr(document.getElementById(“test”), “classxx”, “xx”)
alert(attr(document.getElementById(“test”),”classxx”));

技巧五之getElementsByClassName.

应用案例 :以前js没什么框架的时候,大家都再模仿这个方法,看看今天我是怎么高效的模仿出它来.这也不愧是js初学者的经典代码

(function () {
   var getElementsByClassName=function(cls,context){
      var root = context || document;
    return document.querySelectorAll ? root.querySelectorAll("." + cls) : root.getElementsByClassName ?
           root.getElementsByClassName(cls) : help("*", cls, context);
   }
   var help=function(tagName,cls,context){
        var root= context || document,
            ret=[],elems,i,
            rcls=new RegExp("^|\\s+"+cls+"\\s+|$");
        elems = root.getElementsByTagName(tagName || "*");
        for(i=elems.length;i--;){
            if(rcls.test(elem[i].className)){
              ret.push(elems[i]);
            }
        }
        return ret;
   }
})();

以上几个js淫荡技巧还是蛮实用的,前提是你没用使用别人的js框架,用原生创造效率为前提的代码.

[转载]Android实现类似QQ列表

mikel阅读(1460)

[转载]【Android】实现类似QQ列表 – 朱红的泪 – 博客园.

感谢豆子兄弟的指点 . 本文是参照他的代码在经过一点改造 .

豆子的灵感是来自于 Google 的 Contacts( 联系人 ) 的实现 , 其中有一个类叫做 PinnedHeaderListView( 固定头的列表),本文也不做过多的解释,因为Google的程序中已经写得很清楚了.

本列表是根据自己需要专门实现类似于QQ列表的功能,所以可能扩展性不是很强,自己满足就好.

QQ: 列表显示效果

/**

* QQ 列表

* @author 朱红的泪 (V5)

*

*/

public class QQListView extends ExpandableListView implements OnScrollListener,

OnGroupClickListener {

public QQListView(Context context, AttributeSet attrs, int defStyle) {

super (context, attrs, defStyle);

registerListener();

}

public QQListView(Context context, AttributeSet attrs) {

super (context, attrs);

registerListener();

}

public QQListView(Context context) {

super (context);

registerListener();

}

/**

* Adapter 接口 . 列表必须实现此接口 .

*/

public interface QQHeaderAdapter {

public static final int PINNED_HEADER_GONE = 0;

public static final int PINNED_HEADER_VISIBLE = 1;

public static final int PINNED_HEADER_PUSHED_UP = 2;

/**

* 获取 Header 的状态

*

* @param groupPosition

* @param childPosition

* @return

*        PINNED_HEADER_GONE,PINNED_HEADER_VISIBLE,PINNED_HEADER_PUSHED_UP 其中之一

*/

int getQQHeaderState( int groupPosition, int childPosition);

/**

* 配置 QQHeader, 让 QQHeader 知道显示的内容

*

* @param header

* @param groupPosition

* @param childPosition

* @param alpha

*/

void configureQQHeader(View header, int groupPosition,

int childPosition, int alpha);

/**

* 设置组按下的状态 .

*

* @param groupPosition

* @param status

*/

void setGroupClickStatus( int groupPosition, int status);

/**

* 获取组按下的状态

*

* @param groupPosition

* @return

*/

int getGroupClickStatus( int groupPosition);

}

private static final int MAX_ALPHA = 255;

private QQHeaderAdapter mAdapter ;

/**

* 用于在列表头显示的 View,mHeaderViewVisible 为 true 才可见

*/

private View mHeaderView ;

/**

* 列表头是否可见

*/

private boolean mHeaderViewVisible ;

private int mHeaderViewWidth ;

private int mHeaderViewHeight ;

public void setHeaderView(View view, float height) {

mHeaderView = view;

AbsListView.LayoutParams lp = new AbsListView.LayoutParams(

ViewGroup.LayoutParams. MATCH_PARENT , ( int ) height);

view.setLayoutParams(lp);

if ( mHeaderView != null ) {

setFadingEdgeLength(0);

}

requestLayout();

}

private void registerListener() {

setOnScrollListener( this );

setOnGroupClickListener( this );

}

/**

* 点击 HeaderView 触发的事件

*/

private void headerViewClick() {

long packedPosition = getExpandableListPosition( this

.getFirstVisiblePosition());

int groupPosition = ExpandableListView

.getPackedPositionGroup(packedPosition);

if ( mAdapter .getGroupClickStatus(groupPosition) == 1) {

this .collapseGroup(groupPosition);

mAdapter .setGroupClickStatus(groupPosition, 0);

// 没设置这个会出现一些奇怪的问题 , 暂时不知道为什么

this .setSelectedGroup(groupPosition);

}

}

private float mDownX ;

private float mDownY ;

/**

* 如果 HeaderView 是可见的 , 此函数用于判断是否点击了 HeaderView, 并对做相应的处理 ,

* 因为 HeaderView 是画上去的 , 所以设置事件监听是无效的 , 只有自行控制 .

*/

@Override

public boolean onTouchEvent(MotionEvent ev) {

if ( mHeaderViewVisible ) {

switch (ev.getAction()) {

case MotionEvent. ACTION_DOWN :

mDownX = ev.getX();

mDownY = ev.getY();

if ( mDownX <= mHeaderViewWidth && mDownY <= mHeaderViewHeight ) {

return true ;

}

break ;

case MotionEvent. ACTION_UP :

float x = ev.getX();

float y = ev.getY();

float offsetX = Math.abs(x – mDownX );

float offsetY = Math.abs(y – mDownY );

// 如果 HeaderView 是可见的 , 点击在 HeaderView 内 , 那么触发 headerClick()

if (x <= mHeaderViewWidth && y <= mHeaderViewHeight

&& offsetX <= mHeaderViewWidth

&& offsetY <= mHeaderViewHeight ) {

if ( mHeaderView != null ) {

headerViewClick();

}

return true ;

}

break ;

default :

break ;

}

}

return super .onTouchEvent(ev);

}

@Override

public void setAdapter(ExpandableListAdapter adapter) {

super .setAdapter(adapter);

mAdapter = (QQHeaderAdapter) adapter;

}

/**

* 点击了 Group 触发的事件 , 要根据根据当前点击 Group 的状态来

*/

@Override

public boolean onGroupClick(ExpandableListView parent, View v,

int groupPosition, long id) {

if ( mAdapter .getGroupClickStatus(groupPosition) == 0) {

mAdapter .setGroupClickStatus(groupPosition, 1);

parent.expandGroup(groupPosition);

parent.setSelectedGroup(groupPosition);

} else if ( mAdapter .getGroupClickStatus(groupPosition) == 1) {

mAdapter .setGroupClickStatus(groupPosition, 0);

parent.collapseGroup(groupPosition);

}

// 返回 true 才可以弹回第一行 , 不知道为什么

return true ;

}

@Override

protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {

super .onMeasure(widthMeasureSpec, heightMeasureSpec);

if ( mHeaderView != null ) {

measureChild( mHeaderView , widthMeasureSpec, heightMeasureSpec);

mHeaderViewWidth = mHeaderView .getMeasuredWidth();

mHeaderViewHeight = mHeaderView .getMeasuredHeight();

}

}

private int mOldState = -1;

@Override

protected void onLayout( boolean changed, int left, int top, int right,

int bottom) {

super .onLayout(changed, left, top, right, bottom);

final long flatPostion =getExpandableListPosition(getFirstVisiblePosition());

final int groupPos = ExpandableListView

.getPackedPositionGroup(flatPostion);

final int childPos = ExpandableListView

.getPackedPositionChild(flatPostion);

int state = mAdapter .getQQHeaderState(groupPos, childPos);

if ( mHeaderView != null && mAdapter != null && state != mOldState ) {

mOldState = state;

mHeaderView .layout(0, 0, mHeaderViewWidth , mHeaderViewHeight );

}

configureHeaderView(groupPos, childPos);

}

public void configureHeaderView( int groupPosition, int childPosition) {

if ( mHeaderView == null || mAdapter == null

|| ((ExpandableListAdapter) mAdapter ).getGroupCount() == 0) {

return ;

}

int state = mAdapter .getQQHeaderState(groupPosition, childPosition);

switch (state) {

case QQHeaderAdapter. PINNED_HEADER_GONE : {

mHeaderViewVisible = false ;

break ;

}

case QQHeaderAdapter. PINNED_HEADER_VISIBLE : {

mAdapter .configureQQHeader( mHeaderView , groupPosition,

childPosition, MAX_ALPHA );

if ( mHeaderView .getTop() != 0) {

mHeaderView .layout(0, 0, mHeaderViewWidth , mHeaderViewHeight );

}

mHeaderViewVisible = true ;

break ;

}

case QQHeaderAdapter. PINNED_HEADER_PUSHED_UP : {

View firstView = getChildAt(0);

int bottom = firstView.getBottom();

// intitemHeight = firstView.getHeight();

int headerHeight = mHeaderView .getHeight();

int y;

int alpha;

if (bottom < headerHeight) {

y = (bottom – headerHeight);

alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;

} else {

y = 0;

alpha = MAX_ALPHA ;

}

mAdapter .configureQQHeader( mHeaderView , groupPosition,

childPosition, alpha);

if ( mHeaderView .getTop() != y) {

mHeaderView .layout(0, y, mHeaderViewWidth , mHeaderViewHeight

+ y);

}

mHeaderViewVisible = true ;

break ;

}

}

}

@Override

protected void dispatchDraw(Canvas canvas) {

super .dispatchDraw(canvas);

if ( mHeaderViewVisible ) {

drawChild(canvas, mHeaderView , getDrawingTime());

}

}

@Override

public void onScroll(AbsListView view, int firstVisibleItem,

int visibleItemCount, int totalItemCount) {

final long flatPos = getExpandableListPosition(firstVisibleItem);

int groupPosition = ExpandableListView.getPackedPositionGroup(flatPos);

int childPosition = ExpandableListView.getPackedPositionChild(flatPos);

configureHeaderView(groupPosition, childPosition);

}

@Override

public void onScrollStateChanged(AbsListView view, int scrollState) {

}

}

邮箱:weiwutan@gmail.com

[转载]B2C电子商务系统研发——商品SKU分析和设计(一)

mikel阅读(1405)

[转载]B2C电子商务系统研发——商品SKU分析和设计(一) – 颜超敏 – 博客园.

一、SKU及相关概念定义

在设计商品SKU之前,首先让我们熟悉一下SKU和相关的一些概念。

# 什么是SKU:

SKU=Stock Keeping Unit(库存量单位)

同一型号的商品,或者说是同一个产品项目(商品条形码是针对企业的产品

项目来进行定义的),因为产品与产品之间有某些属性不同,用以区别开这些

不同商品的属性即商品变异属性,又称作SKU属性,因为它决定了SKU

的绝对数量。

# 参考说明

百度上有一篇文章也有阐述,可以做关联阅读,我就不重复贴上了。

http://www.cnblogs.com/winstonyan/admin/EditPosts.aspx

# 什么是SKU属性和选项

比如某件衣服有多种颜色、多种尺码,这些属性会直接关联价格和库存的,

系统会根据该商品关联的SKU属性的某个组合生成SKU。

比如某个款式的衬衫,有XL/L/XXL三种大小,有红黄蓝三种颜色。

对应这里例子,尺码和颜色都是是SKU属性。

对应尺码的XL/L/XXL等,都是SKU属性选项。

【注】上述的属性不一定在任何时候都是SKU属性,看实际的商品情况和设置。

比如对于尺码,某种商品是均码的。那么就不需要创建尺码这个SKU属性了,

而是设置为普通属性,仅作为显示用。

# 什么是商品SKU

商品SKU实际上就是SKU,为了避免误解和SKU属性混淆,我用商品SKU来命名,

表示从属于商品的、实际销售和存储的子实体。

一个商品SKU,表示该商品关联的若干SKU属性的的属性值的某个组合所形成的

子实体。

如对应上面的例子,其中的一种组合 XL + 红色 就会形成一个商品SKU。然后,

我们可以在该实体上管理价格、库存、专门的图片等信息。

# 什么是商品变异

英文名:Product Variants

商品变异其实就是商品SKU,只不过在某些技术文章中这样定义了。即以“变异”

来表达商品SKU的生成。

# 属性集

B2C电子商务系统研发——商品SKU分析和设计(一)

Attribute Set,用于管理各类扩展属性的集合,其中SKU属性也是在管理范畴之内。

商品通过关联属性集而获得该属性集设置好的SKU属性,然后才可以根据这些SKU属性

生成商品SKU。

属性集也成为产品类型。

常见的属性集有:服装、PC、家具、图书等。

# 概念的统一

虽然不同的研究人员有不同的命名和定义,但为了在本系列文章中不出现混淆,

我对概念做了以下统一定义:

1)SKU(或称商品SKU)指的是商品子实体。

2)商品和商品SKU是主次关系,一个商品包含若干个商品SKU子实体,商品SKU从属于商品。

3)SKU不是编码,每个SKU包含一个唯一编码,即SKU Code,用于管理。

4)商品本身也有一个编码,即Product Code,但不作为直接库存管理使用。
有时为了方便管理,会通过商品的Product Code作为前缀生成SKU Code。

二、SKU属性的管理

常见有几种情况:

  1. 商品独立管理
    即SKU属性从属于商品。
    优点:基本上没有。
    缺点:这种比较不靠谱,因为会导致工作量过大。虽然可以通过“复制”功能来稍稍
    简化,但依然不会很理想。所以基本不会采用。
  2. 商品独立关联
    即SKU属性是公共的,每个商品根据自己的实际情况来关联若干个SKU属性,
    然后选择若干选项生成商品SKU。
    优点:灵活,设置好了若干SKU属性和相关选项后,由商品自行选择相关的组合。
    缺点:太灵活了,容易出错。比如对于颜色这个SKU属性而言,由于是公共的,所以
    会定义数十个,甚至更多,但是对于ipad而言,其实只需要2个即可。这种
    管理方式,无法从源头控制商品添加时SKU属性和选项的范围。
  3. 通过属性集管理
    即SKU属性依附属性集存在,不存在可以从属于多个属性集的SKU属性。
    优点:容易进行严格的管理,不易出错。比如同样是命名为“颜色”的SKU属性会存在多个
    (属性ID/编码不同),但是对于ipad的只需要2个选项(黑、白),对于服装则会很多。
    缺点:属性集管理的工作量会稍大,适合属性集不多的系统。
  4. 通过属性集关联
    即SKU属性是公共的,通过属性集关联。
    优点:这种方式重用性不错,对于SKU选项较多的,会简化工作量。
    缺点:由于SKU属性是公共的,所以会存在上面第2点的缺点。但我们也可以通过设置多个
    同名、但编码不同的SKU属性,如多个“颜色”SKU属性,但是选项不同。
  5. 创建独立的SKU属性集
    即设计一个独立的SKU属性集的实体,关联或者直接管理SKU属性。
    这种方式也是挺不错的。因为虽然各种商品类型之间产品特性会有较大的不同,但是SKU属性
    却有可能差异不大。比如对于服装、ipad都存在通过颜色来区分SKU,但是ipad只需要2种选项
    (以后也许微调为3~4种等),而服装则很多了。

三、我的选择

上面介绍了五种形式,我个人倾向第4和第5种,上一篇文章的ER图也改成了第4种的形式,比较符合

常见的思路。第5种也是不错的。可以在接下来的设计分析一下。

[转载]Node.js 的安装和控制台命令

mikel阅读(973)

[转载]Node.js 的安装和控制台命令 – 坐看云起 – 博客园.

一、Node.js简介

为了说服您阅读这份简单的说明,我想先给Node.js做点广告还是有必要的。先看看都有谁在用Node.js吧,跟着业界老大混,总是大差不差的。首 先,微软的云服务Azure已经开始支持Node.js、还有就是Ebay、Yahoo、Linkedin,是不是有点精神了?嗯,我们继续。下面是官网的自我介绍:


Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.

Node.js 是一个平台,建立在 Chrome JavaScript 运行时之上,目的是建立快速、可伸缩的网络应用。Node.js使用事件驱动,非阻塞的 I/O 模式,这使得他是轻量而高效的,能够在分布设备上完美运行高数据吞吐的实时应用。


如果官网没有王婆卖瓜的话,我想您应该有兴趣花上五分钟浏览下面的内容了。

二、安装

这里仅说明Windows平台安装,很简单,去官网下载安装包,运行,OK。

安装完毕后,也许您习惯的去找快捷方式。不好意思,虽然您眼神比我好,可是也是找不到滴。别着急,安装包默认把所有文件拷贝到了 “C:\Program Files\nodejs”目录下,然后,很体贴地设置好了系统环境变量里的path,现在,请打开一个命令行窗口,输入:

>node

>

如果你得到一个提示符,系统没警告说“对不起,没有此命令”,那么,恭喜,安装成功完成。如果你还想观察下nodejs的目录结构的话,也会发现超级简单,就是一个node.exe文件,还有比这更简单的吗?

啊?您说还有一个 npm.cmd 批处理文件和 node_modules 文件夹?那个,那个我们目前可以暂时无视。您可以删了他们而不影响 node.js 的使用。

不过既然提到他们,npm 是用来管理模块的,而 node.js 的魅力也在于他的模块化,内核只做内核该做的事,其他的一切,全通过模块的扩展来实现。这也为 node.js 带来了无尽可能。

可爱的模块们,一般就窝在 node_modules 文件夹里。

三、控制台命令和REPL

好了,既然我们得到了 node.js 的提示符,也就是说我们现在是在 node.js 的环境里了。在这个环境里,您所有的 Javascript 知识都可以派上用场了,因为 node.js 使用的就是著名的 v8 引擎。补充说明一下,node.js 把这个称作Read-Eval-Print-Loop,就是说,咱们在这里就可以交互地跑 js 代码了。感觉回到了最初学 Basic 的年代啊。怀念、怀念。

> a = [ 1, 2, 3]; 
[ 1, 2, 3
> a.forEach(function (v) {
 ... console.log(v);
... });
 1
 2
 3
为数不多的几个控制台命令可以用 help 查到,唯一需要注意的就是别忘了这些命令前面需要带上一个小圆点:
noderepl

好了,现在可以开始您的 node.js 之旅了,当然,如果你愿意的话。

[转载]使用JS获取FCKEditor中的内容

mikel阅读(1056)

[转载]使用JS获取FCKEditor中的内容 .

  1. / 获取FCKEditor中的内容: content为Editor实例的ID
  2. function getEditorContents(){
  3. var oEditor = FCKeditorAPI.GetInstance(“content”);
  4. alert(oEditor.GetXHTML(true));
  5. }
  6. // 向编辑器插入指定代码
  7. function insertHTMLToEditor(codeStr){
  8. var oEditor = FCKeditorAPI.GetInstance(“content”);
  9. if (oEditor.EditMode==FCK_EDITMODE_WYSIWYG){
  10. oEditor.InsertHtml(codeStr);
  11. }else{
  12. return false;
  13. }
  14. }
  15. // 统计编辑器中内容的字数
  16. function getLength(){
  17. var oEditor = FCKeditorAPI.GetInstance(“content”);
  18. var oDOM = oEditor.EditorDocument;
  19. var iLength ;
  20. if(document.all){
  21. iLength = oDOM.body.innerText.length;
  22. }else{
  23. var r = oDOM.createRange();
  24. r.selectNodeContents(oDOM.body);
  25. iLength = r.toString().length;
  26. }
  27. alert(iLength);
  28. }
  29. // 执行指定动作
  30. function ExecuteCommand(commandName){
  31. var oEditor = FCKeditorAPI.GetInstance(“content”) ;
  32. oEditor.Commands.GetCommand(commandName).Execute() ;
  33. }
  34. // 设置编辑器中内容
  35. function SetContents(codeStr){
  36. var oEditor = FCKeditorAPI.GetInstance(“content”) ;
  37. oEditor.SetHTML(codeStr) ;
  38. }
作者: Sjolzy