Knockout Mvc Compoment FrameSet With Typescript

Knockout Mvc Compoment FrameSet With Typescript

框架简介

以下简称为 kot。kot是一套基于knockout jquery bootstrap(Metronic) typescript 的可快速推广的完整前端框架。

框架优势

框架文件结构

1、网站文件结构

App_Data

App_Start

应用程序启动时自动调用的配置。

BundleConfig.cs mvc压缩css和js的配置文件。

FilterConfig.cs mvc过滤器配置,站点地图权限登录菜单管理的入口。

RotuteConfig.cs 路由规则配置。

Webstack 文件夹下含有

ExceptionFilter.cs 网站程序异常处理过滤器。

        MvcMenuFilter.cs 框架权限验证和菜单处理主要程序。

        WebSiteExcetpion.cs 自定义网站错误类型。用于抛出登信息丢失等自定义业务异常。

assest

网站主要引用的资源文件(样式,常用组件等)。现应用的是metronic_v4.1.0。

Admin 管理框架模板

Global bootstrap全局引用资源

Public 网站常用资源,如暂无图片和一些补丁css

Controller

网站控制器

DictionaryFiles

安装盘古分词的字典,如不用分词搜索则可以删除。

Filters

Mvc过滤器文件夹,里面可以自定义一些过滤器,自定义的过滤器需要在App_Start下的FilterConfig.cs中注册。

Scripts

框架依赖js文件和ts文件。

UserControls

业务需要调用的用户控件。

Views

Mvc 视图文件夹,母版页在 Shared 为 _Layout.cshtml

其他为业务视图。

BaseHttpApplication.cs

定义一些 HttpModule的自定义方法,目前在框架应用中,主要用于处理通用上传和下载。

Global.asax

全局配置入口。

SiteInfo.cs

网站通用信息快捷调用方式,比如系统id、当前登录用户等。

Web.config

网站配置文件。

 

2、模型文件结构

通过业务区分业务文件夹。

3、业务文件结构

4、单元测试文件结构

框架基础

登录原理

登录流程:

1、【统一登录】像Session中添加登录信息

2、【网站过滤器验证】 (MvcMenuFilter.cs 需要在FilterConfig中注册)

每一个action请求时会判断当前Session中是否有登录信息。

如果登录信息为空,判断请求方式是同步请求还是异步请求。

同步请求,则通过Redrect 跳转到登录页。

异步请求,则通过throw new WebSiteException ,错误码为1000.

3、异常处理。

所有网站异常都会被ExceptionFilter.cs捕获。

ExceptionFilter 可以记录错误日志,日志路径 在网站跟目录的Log文件夹下。

自定义异常,字典也声明在这个文件中。

public static Dictionary<string, string> ErrorDictionary = new Dictionary<string, string>()

{

{ "1000","登录信息丢失"}

};

如果抛出的是WebSiteException则会通过字典方式相应。

这个抛出的结果会被框架通WebUtil.js响应。(后面会详细说明)

菜单生成原理

登录获取的用户信息中会包含菜单信息。

获取当前系统编号后,可以通过系统编号拿到该用户当前登录系统的菜单,并转换成List<LoginMenuInfo>这个数组。

这个数组会通过ViewBag.treeData (json格式)传递到页面。

页面接收到数据时会通过Menu.js 响应,来生成菜单和面包屑导航。

对应的模板在 /Views/ Shared/ PartialViews/ _Left.cshtml 中。

自定义面包屑显示

比如标签管理这个三级面包屑在 菜单中并没有

可以通过sitemap 这个固定参数来传递。

<a data-bind="attr:{href:formatUrl('/PackageConfig/TagManage?packageId='+item.Id()+'&sitemap='+encodeURI('套餐研发~套餐设计器~标签管理'))}"标签管理</a>

以上会配合 下面 Menu.js使用

js控件

通用Web工具,常用js方法等。常用方法有WebUtil.ajax();参数同$.ajax参数,比他多2个参数为 loading和loadmsg。实现loading效果,依赖loading.js和jquery.sh.popups.js。

列举一下常用方法:

下面列举ts版本api

接口

interface WebUtilAjaxOption extends JQueryAjaxSettings {

/**

* 是否显示等待 默认 true

*/

loading?: boolean;

/**

* 等待时的提示信息 默认 加载中...

*/

loadmsg?: string;

}

 

interface WebUtilStatic {

/**

* 转义html字符

* @param str

* @returns string

*/

encodeHtml(str: string): string;

WebUtil.encodeHtml("<div></div>"); 返回string类型。

结果会把 < 这种尖角号 转换为 &lt; 这种符号。

/**

* 停止冒泡事件

* @param {} event

* @returns {}

* 作者:崔园清

* 小组:山河web

* 说明:停止冒泡事件;

* 创建日期:2014-9-27 14:59:17

* 版本号:v1.0

*/

stopEvent(event: any): void;

 

/**

* 将一个string值转换为时间

* @param {} value

* @returns {}

*/

parseDate(value: string): Date;

把一个字符串转换成时间类型。

var str="2015-09-10 18:30"; var date = WebUtil.parseDate(str);

/**

* 获取url的参数

* @param {} name

* @returns {}

*/

getQueryString(name: string): string;

获取链接中的QueryString参数。

  1. WebUtil.getQueryString("id"); // 返回 001

    WebUtil.getQueryString("name");// 返回 bob

    WebUtil.getQueryString("ddd");// 返回 空字符串""

/**

* ajax

* @param option

* @returns {}

*/

ajax(option: WebUtilAjaxOption);

ajax中定义了一个错误的Handler 其中包含一些通用的自定义错误码

errorHandler = {

"1000": function () {

sh.alert("您的登录已失效,请重新登录。", function () {

location.href = "/Account/Login";

});

},

"1001": function () {

sh.alert("您的企业信息丢失,请重新选择企业。", function () {

location.href = formatUrl("/Home/EnterpriseSet");

});

},

"1002": function () {

sh.alert("您已在当前企业离职。", function () {

location.href = formatUrl("/Home/EnterpriseSet");

});

}

};

是以用WebUtil.ajax调用后台方法时如后台程序抛出异常,则会判断错误码,

如果错误码中的ErrorCode 符合通用Handler时则会调用Halder的方法

如果不包含errorCode但是包含 ErrorMessage 则是wcf接口抛出的通用异常,会直接弹出提示。

果即不包含错误码也不包含ErrorMessage则是404之类的调用异常。

抛出 调用ajax异常时 则是前端controller或者business出错,如果直接提示的错误信息则是wcf服务异常。

 

//输出错误信息到控制台

console.log(xhr.responseText);

//默认行为,弹出提示

try {

var errorJson = $.parseJSON(xhr.responseText);

if (errorJson.errorCode != null) {

var errorFun = errorHandler[errorJson.errorCode];

if (errorFun != null) {

errorFun();

} else {

try {

var firstMsgJson = errorJson.errorMessage.match(/\{[^{}]+\}/)[0];

var serviceError = $.parseJSON(firstMsgJson);

sh.alert(serviceError.ErrorMessage);

} catch (e) {

sh.alert("调用ajax异常,请查看程序日志:" + errorJson.errorMessage);

}

}

} else {

sh.alert('服务调用错误,请查看控制台。');

}

} catch (e) {

sh.alert('服务调用错误,详情请见错误日志。');

}

 

 

/**

* 获取序号方法

* @param {} index

* @param {} pageIndex

* @param {} pageSize

* @returns {}

*/

getNum(index: number, pageIndex: number, pageSize: number): number;

一个在分页状态下获取连续序号的方法。这里$parent 是knockout上下文对象。$index是knockout循环上下文中的索引。

<tbody data-bind="foreach:{data:dataList,as:'item'}">

<tr>

<td>

<!--通用获取序号方法-->

 

<p class="form-control-static"><!--ko text:WebUtil.getNum($index(),$parent.dataList.pageIndex(),$parent.dataList.pageSize())--><!--/ko--></p>

</td>

 

declare var WebUtil: WebUtilStatic;

 

interface shStatic {

/**

* 通用提示框方法

* @param msg

* @param callback

* @param msgtitle

* @returns {}

*/

alert(msg: string, callback?: () => void, msgtitle?: string): void;

普通提示框: title 默认为 "系统提示"

1)sh.alert("操作成功!");

2)sh.alert("操作完成!",function(){console.log("数据操作完成") });

3)sh.alert("操作完成!",function(){console.log("数据操作完成") },"DaTree提示");

confirm(msg: string, yescallback: () => void, nocallback?: () => void, msgtitle?:
string): void;

包含确定取消按钮的提示框:title 默认为"系统提示"

1)sh.confirm("确定要这么做吗?",function(){ console.log("点击了确认") });

2)sh.confirm("确定要这么做吗?",function(){ console.log("点击了确认") },function(){console.log("点击了取消")});

3) sh.confirm("确定要这么做吗?",function(){ console.log("点击了确认") },function(){console.log("点击了取消")},"DaTree提示自定义标题");

4) 不要取消事件可以传null

sh.confirm("确定要这么做吗?",function(){ console.log("点击了确认") },null,"DaTree提示自定义标题");

declare var sh: shStatic;

 

interface KnockoutStatic {

/**

* 注册控件通用方法

* @param controlName

* @param viewModel

* @param templateUrl

* @returns {}

*/

RegisterControl(controlName: string, viewModel: any, templateUrl: string): void;

1)通用注册控件方法

//注册控件

ko.RegisterControl("priceconfigcontrol", PriceConfigControlViewModel, formatUrl("/UserControls/PackageConfig/PriceConfigControl/PriceConfigControlView.html"));

2)registerControl方法会造成很多次异步html请求,正在想办法解决。

formatCurrency

/**

* 将数字转换为 格式化后的金钱字符串

* @param num

*/

declare function formatCurrency(num: number): string;

  1. 使用场景一般是在html绑定一个金钱格式的数字时用的。

    <div class="col-md-3 ">

    <p class="form-control-static">

    套餐必选成本合计:<!--ko text:formatCurrency(priceConfigModel().RequiredCostTotalPrice())--><!--/ko-->

    </p>

    </div>

formatUrl

全局处理虚拟目录的方法。在layout上实现。

/**

* 处理虚拟目录格式化地址的方法 在Layout上实现

* var appRoot = "@Request.ApplicationPath";

* if (!appRoot) {

* throw new Error("请设置全局变量.");

* }

*

* function formatUrl(url) {

* if (url == null) {

* return url;

* }

* if (window.appRoot && window.appRoot != '/' && url.indexOf("/") == 0) {

* if (url.indexOf(appRoot + "/") != 0) {

* url = appRoot + url;

* }

* }

* return url;

* }

* @param url

*/

declare function formatUrl(url: string): string;

比如当前网站是在虚拟目录下

http://www.baidu.com/myWebSite/

那么在调用url时 需要加上 formatUrl();

$.ajax({

    url:formatUrl("/package/getpackage")

data:{}

});

这里 formatUrl返回为 /myWebSite/package/getpackage

Guid

/**

* 定义一个Guid接口

*/

interface GuidStatic {

Empty: string;

}

/**

* 定义一个Guid静态类

*/

declare var Guid: GuidStatic;

Guid在js中没有默认值,有的时候后台参数需要 反序列化一个 Id类型,所以添加了一个Guid默认值

下面为demo

constructor(model?) {

this.Id = ko.observable(model && model.Id != null ? model.Id : Guid.Empty);

}

string.format

/**

* 定义String静态方法

*/

interface StringConstructor {

format: (...args: any[]) => string;

}

这里与C#中静态调用稍有不同。

C#中 : string str=string.Format("今天是{0}国庆节","10月1号");

js中: var str="今天是{0}国庆节".format("10月1号");

Date通用

简单例子: var date1=new Date();

date1.format("yyyy-mm-dd HH:mm");

/**

* 时间通用处理

*/

interface Date {

/**

* 格式化时间

* @param format

* @returns {}

*/

format(format: string): string;

/**

* 添加年

* @param value

* @returns {}

*/

addYear(value: number): Date;

/**

* 添加月

* @param value

* @returns {}

*/

addMonth(value: number): Date;

/**

* 添加天

* @param value

* @returns {}

*/

addDays(value: number): Date;

/**

* 添加小时

* @param value

* @returns {}

*/

addHours(value: number): Date;

/**

* 添加分

* @param value

* @returns {}

*/

addMinutes(value: number): Date;

/**

* 获取今天

* @param value

* @returns {}

*/

getToday(): Date;

}

KnockoutPaging扩展

/**

* 为kopaging 插件做的扩展

*/

interface KnockoutObservableArrayFunctions<T> {

/**

* 扩展了ko paging之后才有的属性

*/

pageIndex: KnockoutObservable<number>;

/**

* 扩展了ko paging之后才有的属性

*/

pageSize: KnockoutObservable<number>;

/**

* 扩展了ko paging之后才有的属性

*/

callback: () => void;

/**

* 设置数据总条数

* @param count

* @returns {}

*/

SetPageTotal: (count: number) => void;

}

KeyValuePair

 

一般用于字典类型的数据处理

Demo

/**

* 选区类型字典

*/

categoryAreaDic: KnockoutObservableArray<KeyValuePair2<string, string>>;

api

/**

* 键值对

*/

interface KeyValuePair2<TKey, TValue> {

Key: TKey;

Value: TValue;

}

 

/**

* 键值对参数对象

*/

interface IKeyVaulePair {

Key: any;

Value: any;

}

 

/**

* 键值对委托方法

*/

declare var KeyValuePair2: (obj: IKeyVaulePair) => void;

EventBus事件总线

/**

* 事件总线接口

*/

interface EventBusStatic {

/**

* 注册事件

* @param option

* @returns {}

*/

registerEvent(option: EventBusOption): EventBusStatic;

 

/**

* 调用事件

* @param eventID

* @param args 参数列表

* @returns void

*/

callEvent(eventID: string, ...args: Array<any>): void;

/**

* 打印事件列表

* @returns {}

*/

print(): Array<EventBusOption>;

/**

* 删除事件

* @param eventID

* @returns {}

*/

removeEvent: (eventID: string) => void;

/**

* 验证事件是否存在

* @param eventID

* @returns true存在

*/

eventExist: (eventID: string) => boolean;

}

/**

* 事件总线对象

*/

declare var EventBus: EventBusStatic;

demo

var obj1 = { name: "123", say: function (title, msg) { debugger; alert(title + ":" + msg + "name:" + this.name); } };

EventBus.registerEvent({ eventHandler: obj1, eventName: "say2", eventId: "s1", eventBody: obj1.say });

 

EventBus.registerEvent({ eventHandler: obj1, eventName: "say2", eventId: "s2", eventBody: obj1.say });

 

EventBus.registerEvent({ eventHandler: obj1, eventName: "say2", eventId: "s3", eventBody: obj1.say });

EventBus.callEvent("s2", "标题", "消息");

  1. loading.js

接口

显示等待框方法

/**

* loading插件接口

*/

interface loadingStatic {

/**

* 打卡loading

* @param text 显示的文字

* @returns $loading

*/

open(text?: string): JQuery;

/**

* 关闭等待框

* @returns $loading

*/

close(): JQuery;

/**

* 一定要在 dom ready之前调用,否则无效。

* @param url loading 图片的路径 默认为 imgs/loading.gif

* @returns $loading

*/

setImageUrl(url: string): JQuery;

}

 

declare var loading: loadingStatic;

例子

/*

* 开启 loading.open();

* 关闭 loading.close()

* 设置loading图片 loading.setImageUrl("/Content/Images/loading1.gif");

  1. jquery.sh.popups.js

接口

/**

* 弹窗组件参数

*/

interface popupsOptions {

listeners?: {

show?: () => void;

hide?: () => void;

};

width?: number;

}

 

/**

* 弹窗组件对象

*/

interface popups {

show: () => popups;

hide: () => void;

}

 

interface JQuery {

/**

* 弹窗插件

* @param options

* @returns {}

*/

popModal(options?: popupsOptions): popups;

 

}

例子

/*

var modalChooseBuilding = $("#modalChooseBuilding").popModal({

listeners: {

show:function() {

},

hide:function() {

}

},

width: 1200

});

modalChooseBuilding.show(); 显示弹窗

modalChooseBuilding.hide(); 关闭弹窗

*<div id="modalChooseBuilding" class="pop-modal">

<div class="modal-header">

<h4 class="modal-title">标题</h4>

</div>

<div class="modal-body">

 

</div>

<div class="modal-footer">

<button class="btn btn-primary" >确定</button>

<button type="button" class="btn btn-default btn_enter" onclick="modalChooseBuilding.hide();">关闭</button>

</div>

</div>

*/

  1. simpleValidate.js

接口

 

/// <reference path="../../jquery/jquery.d.ts" />

 

 

/**

* 参数接口

*/

interface SimpleValidateOption {

 

 

/**

* 成功的样式

*/

successClass?: string;

/**

* 失败的样式

*/

errorClass?: string;

/**

* 失败元素的样式

*/

errorMessageClass?: string;

/**

* 远程验证元素呈现的样式

*/

remoteClass?: string;

 

 

}

 

/**

* simpleValidate 静态方法

*/

interface SimpleValidate {

/**

* 添加规则

* @param rule

* @param message

* @returns void

*/

addRuleMessage(rule: any, message: any);

/**

* 初始化方法

* @param element

* @param options

* @returns {}

*/

init(element: JQuery, options?: SimpleValidateOption);

/**

* 重置方法

* @param element

* @returns {}

*/

reset(element: JQuery);

}

 

interface JQueryStatic {

/**

* 初始化全局JQuery静态变量

*/

simpleValidate: SimpleValidate;

}

 

interface JQuery {

/**

* 验证方法

* @param options

* @returns {}

*/

simpleValidate(options?: SimpleValidateOption): JQuery;

}

例子

初始化验证方法 $.simpleValidate.init($("#modalCopySpaceScheme"));

重置 $.simpleValidate.reset($("#modalCopySpaceScheme"));

验证 if (!$("#modalCopySpaceScheme").simpleValidate()) {

return;

}

  1. jquery.sh.webuploader.js

通用上传控件

Api接口

 

interface ShUploaderServerFile {

responseVal: string;

name: string;

ext: string;

}

 

interface ShUploaderOption {

//对应错误处理时使用的提示信息

errorMessage: any;

//生成的input所使用的NAME

inputName: string;

//此处设为flash时会只支持flash方式,不启用HTML5方式

runtimeOrder?: string;

//服务器回传数据中代表文件的字段名

responseVal: string;

//上传控件备注名称

info?: string;

//文件上传路径(接口地址)

server: string;

//预览上传后文件的根目录或接口地址

previewURL: string;

//MD5秒传设置,为真时会把体积大小超过md5SizeLimit的文件向md5URL发送文件信息并根据结果绝定是不是需要上传文件

md5Check: boolean;

//秒传验证的url

md5URL?: string;

//文件上传域,即在回传POST(GET)的内容中,哪个参数名包含文件

fileVal: string,

//falsh插件路径,初始化插件时需配置此参数,否则FLASH插件会失效

swf: string;

//可以上传文件的总数量限制,默认为1

fileNumLimit: number;

//是否显示可以上传文件的总数量限制文本 默认为 true 显示

isShowfileNumLimit?:boolean;

//单个文件大小限制(此处默认为10M)

fileSingleSizeLimit: number;

//插件总计可以上传多少字节的文件(100M)

fileSizeLimit: number;

//根据服务器回传值创建预览文件服务端地址的URL方法

createFileUrl: (responseVal: string) => string;

//自动开始上传

auto: boolean;

//文件上传方式 false为常规方式,true为启用二进制流

sendAsBinary: boolean;

//[默认值:false] 是否要分片处理大文件上传。

chunked?: boolean;

// [可选] [默认值:5242880] 如果要分片,分多大一片? 默认大小为5M.

chunkSize?: number;

// [可选] [默认值:2] 如果某个分片由于网络问题出错,允许自动重传多少次?

chunkRetry?: number;

//并发上传,默认就让一次传一个 多个需要服务支持

threads: number;

//图片模式

imageMode: boolean;

//支持拖拽模式

dndMode: boolean;

//支持剪切板粘贴

pasteMode: boolean;

//事件处理

listeners: {

//文件上传成功

uploadSuccess: (file: any, response: any) => void;

//文件上传错误

error: (msg: string) => void;

//整体上传完成

complate: () => void;

//结束事件 此处添加上传结束的回调处理函数

finished: () => void;

//此处放置开始上传时调用的事件

startUploader: () => void;

//删除文件事件

removeUploadedFile: (file: any) => void;

};

//允许的文件类型

accept: {

title: string;

extensions: string;

mimeTypes: string;

};

//随上传文件一起回传的参数

formData: any;

//把已存在的文件显示出来,用于在编辑状态下显示已存 的文件

serverFiles: Array<ShUploaderServerFile>;

}

/**

* 上传插件

*/

interface ShUploader {

//控件销毁方法

destroy: () => void;

}

interface JQueryStatic {

/**

* 初始化全局JQuery静态变量

*/

sh: {

uploader: ShUploader;

};

}

 

 

interface JQuery {

shUploader(options?: ShUploaderOption): ShUploader;

}

例子

//上传控件配置1

this.upload1 = $("#file_uploaer_1").shUploader({

//对应错误处理时使用的提示信息

errorMessage: {

"Q_EXCEED_NUM_LIMIT": "只能上传999张图片",

"Q_EXCEED_SIZE_LIMIT": "请上传2M以下的图片",

"Q_TYPE_DENIED": "上传图片格式为: gif jpg png",

"F_DUPLICATE": "您选择了重复的文件",

"F_EXCEED_SIZE": "请上传2M以下的图片"

},

//runtimeOrder: 'flash', //此处设为flash时会只支持flash方式,不启用HTML5方式

inputName: "sh_uploader_val", //生成的input所使用的NAME

responseVal: "revisionId", //服务器回传数据中代表文件的字段名

info: '上传控件1',

server: formatUrl("/Uploads"), //文件上传路径(接口地址)

previewURL: formatUrl("/Files/R"), //预览上传后文件的根目录或接口地址

//MD5秒传设置,为真时会把体积大小超过md5SizeLimit的文件向md5URL发送文件信息并根据结果绝定是不是需要上传文件

md5Check: false,

md5URL: formatUrl("/CheckRepeat"),

fileVal: 'file', //文件上传域,即在回传POST(GET)的内容中,哪个参数名包含文件

swf: formatUrl("/Scripts/SH.Plugin/uploader/webuploader-0.1.5/Uploader.swf"), //falsh插件路径,初始化插件时需配置此参数,否则FLASH插件会失效

fileNumLimit: 999, //可以上传文件的总数量限制,默认为1

fileSingleSizeLimit: 2 * 1048576, //单个文件大小限制(此处默认为10M)

fileSizeLimit: 10000 * 10485764, //插件总计可以上传多少字节的文件(100M)

//根据服务器回传值创建预览文件服务端地址的URL方法

createFileUrl(responseVal) {

return this.previewURL + "/" + responseVal;

},

auto: true, //自动开始上传

sendAsBinary: true, //文件上传方式 false为常规方式,true为启用二进制流

chunked: true, //[默认值:false] 是否要分片处理大文件上传。

chunkSize: 1048576, // [可选] [默认值:5242880] 如果要分片,分多大一片? 默认大小为5M.

chunkRetry: 2, // [可选] [默认值:2] 如果某个分片由于网络问题出错,允许自动重传多少次?

threads: 1, //并发上传,默认就让一次传一个\

//图片模式

imageMode: true,

//支持拖拽模式

dndMode: true,

//支持剪切板粘贴

pasteMode: true,

//事件处理

listeners: {

uploadSuccess(file, response) {

file.filePath = response.data.revisionId;

//self.EdittingPlan().Spaces()[index].SpaceImages.push(response.data.revisionId);

 

self.EdittingPlan().FirstImage(response.data.revisionId);

},

error(msg) {

sh.alert(msg);

},

complate() {

//此处添加上传成功的回调处理函数

},

//结速事件

finished() {

//此处添加上传结束的回调处理函数

loading.close();

},

startUploader() {

//此处放置开始上传时调用的事件

loading.open("文件上传中...");

},

removeUploadedFile(file) {

self.EdittingPlan().FirstImage("");

}

},

//允许的文件类型

accept: {

title: 'Images',

extensions: 'gif,jpg,png',

mimeTypes: 'image/*'

},

//随上传文件一起回传的参数

formData: {},

//把已存在的文件显示出来,用于在编辑状态下显示已存 的文件

serverFiles: (() => {

var result = [];

if (this.EdittingPlan().FirstImage() !== "") {

result.push({

responseVal: this.EdittingPlan().FirstImage(),

name: "",

ext: "jpg"

});

}

return result;

})()

});

  1. Menu.js

通过viewbag 中取过来的数据来初始化菜单

例子

需要引用 jquery tmpl.js

<script>

$(document).ready(function () {

initSitemap(@Html.Raw(ViewBag.treeData),@Html.Raw(ViewBag.SiteMapKeys));

});

</script>

<script id="one" type="text/x-jquery-tmpl">

<li data-mapdata="${Name}">

<a href="javascript:;">

<i class="${Icon}"></i>

<span class="title">

${Name}

</span>

<span class="selected"></span>

<span class="arrow"></span>

</a>

{{if ChildMenuInfos!=null && ChildMenuInfos.length>0}}

<ul class="sub-menu">

{{tmpl(ChildMenuInfos) '#tow'}}

</ul>

{{/if}}

</li>

 

</script>

<script id="maptree" type="text/x-jquery-tmpl">

<li>&nbsp;<a href="javascript:;"> ${$data}</a>&nbsp;</li>

<i class="fa fa-angle-right"></i>

</script>

 

<script id="tow" type="text/x-jquery-tmpl">

<li data-mapdata="${Name}">

<a href="${formatUrl(Url)}">

<i class="${Icon}"></i>

${Name}

</a>

{{if ChildMenuInfos!=null && ChildMenuInfos.length>0}}

<ul class="sub-menu">

{{tmpl(ChildMenuInfos) '#tow'}}

</ul>

{{/if}}

</li>

</script>

 

 

<!--站点地图容器-->

<ul id="menuwarp" class="page-sidebar-menu" data-keep-expanded="false" data-auto-scroll="true" data-slide-speed="200"></ul>

 

  1. linq.js

框架 外部引用非必须组件

可以在js中做 lambda 查询

例子

具体用法请 linq.d.ts;

Enumerable.From(this.SpaceSchemesItems()).Where(x => x.IsDefault()).Sum(x => x.TotalCostPrice());

前端规范

  1. typescript/javascript规范

TSModel规范

  1. Model属性声明,要与后台模型一致(需要提交到后台反序列化的部分一定要一致。)
  2. 计算属性声明,要确定返回值类型,在ts模式下有时候需要强制标识

this.DefaultCount = ko.computed<number>(() => {

return <number>Enumerable.From(this.SpaceSchemesItems()).Count(d => d.IsDefault());

}, this);

  1. 开头字母大写,与后台模型属性名想对应.
  2. 构造函数要添加可空any类型参数。
  3. KnockoutModel模型绑定中枚举类型想绑定checked需要使用string类型。

下面附上标准demo

 

/**

* 套餐列表模型

*/

class PackageListModel {

/**

* 空间类型集合

*/

SpaceTypes: KnockoutObservableArray<string>;

/**

* 创建日期

*/

CreateDate: KnockoutObservable<string>;

/**

* 下架商品数量

*/

OffShelfCount: KnockoutObservable<number>;

/**

* 排序

*/

Sort: KnockoutObservable<number>;

/**

* 颜色

*/

Color: KnockoutObservable<string>;

/**

* id

*/

Id: KnockoutObservable<string>;

/**

* 状态枚举描述

*/

Status: KnockoutObservable<string>;

/**

* 状态枚举id

*/

StatusId: KnockoutObservable<number>;

/**

* 套餐类型 套餐/造型

*/

ModelType: KnockoutObservable<string>;

/**

* 套餐模式 基础/成品/基础+成品

*/

Mode: KnockoutObservable<string>;

 

constructor(model?: any) {

this.SpaceTypes = ko.observableArray([]);

if (model && model.SpaceTypes != null) {

for (var item of model.SpaceTypes) {

this.SpaceTypes.push(item);

}

}

this.CreateDate = ko.observable(model && model.CreateDate != null ? model.CreateDate : "");

this.OffShelfCount = ko.observable(model && model.OffShelfCount != null ? model.OffShelfCount : 0);

this.Sort = ko.observable(model && model.Sort != null ? model.Sort : 0);

this.Color = ko.observable(model && model.Color != null ? model.Color : "");

this.Id = ko.observable(model && model.Id != null ? model.Id : Guid.Empty);

this.Status = ko.observable<string>(model && model.Status != null ? model.Status : "未上架");

this.StatusId = ko.observable<number>(model && model.StatusId != null ? model.StatusId : 0);

}

}

命名规则

1)搜索关键字:小s开头

sPackName,sKeywords,sBilPack。

2)字典:如枚举状态等dic开头

dicShellStatus

3)普通属性,私有变量:驼峰命名

packName

4)列表集合数据源属性:list开头

listPacks

5)临时用缓存属性,如编辑中商品等:temp开头,一定要备注用途

/*

* 编辑商品弹窗绑定商品数据源对象

*/

tempProduct

6)弹窗:modal开头

modalCopySpaceScheme

 

viewModel中也如此声明

//复用选区弹窗对象

modalCopySpaceScheme: popups;

 

<div id="modalCopySpaceScheme" class="pop-modal">

<div class="modal-header">

<h4 class="modal-title">选区复用</h4>

</div>

<div class="modal-body">

</div>

<div class="modal-footer">

<a href="javascript:;" class="btn blue" data-bind="click:function(){eventConfirmCopy();}">确定</a>

<button type="button" class="btn btn-default btn_enter" data-bind="click:modalCopySpaceScheme.hide">取消</button>

</div>

</div>

Typescript书写规范

1)属性与构造相对应:ts中声明的属性是抽象的,需要在构造中实例化。

class PackageListModel {

/**

* 空间类型集合

*/

SpaceTypes: KnockoutObservableArray<string>;

constructor(model?: any) {

    this.SpaceTypes=ko.observableArray([]);

}

2)this作用域:无法区分this作用域时。

var self=this;

  1. html/cshtml规范

html书写规范遵循语义化的标准写法

比如声明一个按钮<button class="btn btn-default">确定</button>

尽量不要写 <a href="javascript:;" class="btn btn-default">确定</a>

 

1)  容器布局:遵循bootstrap的标签套用原则,不应有多余标签。

 

所有内容都应放在row下的col里。

2)  标签页:在Metronic的布局标准下,标签页的容器应为rowcol

 

代码详见Metronic模版。

3)  页面html行数较多时要添加region标签。Ctrl+ks

 

4)  Layout布局容器应在.container

 

5)搜索框应用panel包裹

3 HTML元素命名规范

标签

命名

<input type="text" />

txtName

<select></select>

selProjectState

<texarta></texarea>

textProductDesc

<label></lable>

lbPrice

<div></div>

divProjectFile

<span></span>

spSKUPro

标签页

tabUserManage

模态框

modalAddUser

遮罩层

dialogLoading

   

 

  1. controller business model 规范

  1. Controller 负责相应页面请求,给页面传入字典和接收页面返回值的作用。

2Bussiness调用服务接口,简单逻辑处理,模型转换等。

  1. knockout 组件规范

组件 在项目中被命名为UserControl

  1. userControl编码规则详见viewmodel,他们的声明方式类似,传入参数上只有prarms
  2. userControl最下面一行需要调用控件注册,并指定模版路径。

    以下是通用注册方法,在webutil中声明。

Demo见viewModel。

  1. viewModel规范

    下图是一个ts版的demo

    viewModel 由4部分组成

    1. 属性:包含页面所有需要的数据源,临时属性,搜索条件属性等
    2. 构造:构造中会初始化所有属性声明、字典数据、页面init方法、验证控件、模态框等。
    3. 方法:方法声明的原则为数据交互使用,所有请求controler获取或设置数据的方法(WebUtil.ajax)都写在方法里。
    4. 事件:页面所有元素的事件绑定usercontrol回调,如搜索按钮点击,下拉框等。

属性上都应有注释,如果有依赖关系 可以用 region 扩起来。

下面是方法的例子,这些方法都用于数据交互。(ts中不写返回类型默认为 void)

事件一定要注明用途,控件回调的事件也在这个区域声明。

下面有一个事件叫 eventShowRelation 显示关联套餐弹窗,其中有一行代码,this.modalRelationPackage.show();

这行代码 调用的就是弹窗控件的方法(详见,js控件下的 jquery.sh.popups.js的api)。

控件回调事件写法

控件初始化绑定方法方法。

 

Knockout的组件可以实现自定义html标签的功能。

  1. Knockout 控件库

  1. PagingControl

    用于knockout通用分页。

文件结构

paging.css 控件样式表, PagingControlViewModel,主要逻辑文件,PagingControlView.html控件绑定的html模板。

例子

首先文件引用,在引用了 jquery.js、knockout.js、loading.js、popups.js、webutil.js、的基础上,引用如下文件。

viewModel 构造里面其实还有一些参数不过被我删除了,一般都是字典的传入,对控件本身没影响。

文件引用

<!--paging control-->

<link href="~/UserControls/PagingControl/paging.css" rel="stylesheet" />

<script src="~/UserControls/PagingControl/PagingControlModel.js"></script>

<!--main viewModel 页面主viewModel文件-->

<script src="~/ViewModels/PackageConfig/PackageConfigListViewModel.js"></script>

 

<script>

$(function () {

ko.applyBindings(new PackageListViewModel());

});

</script>

viewModel

首先是模型,不必关注具体有什么,你要在列表上显示哪些列就写什么属性就可以了。

然后主viewModel声明 一个 KnockoutObservableArray 类型的属性。

/**

* 列表数据源

*/

listPackage: KnockoutObservableArray<PackageListModel>;

在构造中实现声明。

this. listPackage = ko.observableArray<PackageListModel>([]).extend({

paging: {

pageIndex: 1,

pageSize: 10,

callback() {

self.fnGetData();

}

}

});

 

注意后面的 extend 是扩展 这个 obarray的。

几个必要的分页参数 pageIndex pageSize,主要是这个callback,这个callback的作用就是当你点击页码时执行的方法。

原理就是在点击页码后执行callback里的 fnGetData();方法来刷新 listPackage这个数据源(koarray,数据源更新页面html也会自动更新。)

 

Ajax Data 参数中事一些搜索用的条件用于获取数据用。分页控件主要还是关注 pageIndex和pageSize

/**

* 获取套餐列表数据源

*/

fnGetData() {

var self = this;

WebUtil.ajax({

url: formatUrl("/PackageConfig/GetPackageList"),

data: {

pageIndex: self.dataList.pageIndex(),

pageSize: self.dataList.pageSize(),

keywords: self.keywords()

},

type: "post",

dataType: "json",

success(data) {

if (data) {

//设置总条数 需要后台返回当前这个查询有多少条数据 以便控件计算页码

self.listPackage.SetPageTotal(data.RowCount);

//清空之前的缓存

self. listPackage.removeAll();

         //循环添加

for (var item of data.Datas) {

             //记得一定不要忘记 new koModel

self.dataList.push(new PackageListModel(item));

}

}

}

});

}

Html绑定

我把完整的结构粘贴下来,实际上只有2行高亮显示的代码是用于pagingcontrol。

思路就是一个table 下的tbody 中 循环列出模型中的项。加一个分页控件的绑定。

(注意代码中 <!—ko *** --><!— /ko--> )是无容器绑定

<div class="table-responsive">

<table class="table table-bordered table-advance">

<thead>

<tr>

<th>

序号

</th>

<th>

模板名称

</th>

<th>

总面积(㎡)

</th>

<th>

<select class="" data-bind="options:packageTypesDic,optionsText:'Value',optionsValue:'Key',optionsCaption:'套餐类型',value:packageType,event:{change:eventSearch}"></select>

</th>

<th>

<select class="" data-bind="options:packageStatesDic,optionsText:'Value',optionsValue:'Key',optionsCaption:'状态',value:packageState,event:{change:eventSearch}"></select>

</th>

<th>

创建时间

</th>

<th>

下架工艺/商品

</th>

<th>

排序

</th>

<th>

竞品标签

</th>

<th>

套餐类别

</th>

<th>

套餐模式

</th>

<th style="width: 100px;">

操作

</th>

</tr>

</thead>

<tbody data-bind="foreach:{data:listPackage,as:'item'}">

<tr>

<td>

<!--通用获取序号方法-->

<p class="form-control-static"><!--ko text:WebUtil.getNum($index(),$parent.dataList.pageIndex(),$parent.dataList.pageSize())--><!--/ko--></p>

</td>

<td>

<p class="form-control-static"> <!--ko text:item.Name--><!--/ko--></p>

</td>

<td>

<p class="form-control-static"> <!--ko text:formatCurrency(item.Area())--><!--/ko--></p>

</td>

<td>

<p class="form-control-static"><!--ko text:item.PackageType--><!--/ko--></p>

</td>

<td>

<p class="form-control-static"> <!--ko text:item.Status--><!--/ko--></p>

</td>

<td>

<p class="form-control-static"> <!--ko text:item.CreateDate--><!--/ko--></p>

</td>

<td>

<p class="form-control-static"><!--ko text:item.OffShelfCount--><!--/ko--></p>

</td>

<td style="width: 90px;">

<input type="text" style="width: 100%;" class="{required:true,integer:true,min:1,max:999999999}" data-bind="value:item.Sort,event:{change:function(){$parent.eventSaveSort($element,item.Id());}}" value="" />

</td>

<td>

<div class="colortag" data-bind="style:{'background-color':'#'+item.Color()}"></div>

</td>

<td>

<p class="form-control-static"><!--ko text:item.ModelType--><!--/ko--></p>

</td>

<td>

<p class="form-control-static"><!--ko text:item.Mode--><!--/ko--></p>

</td>

<td>

<div class="btn-group btn-group-solid btn-group-sm" style="position: absolute;">

<button type="button" class="btn blue dropdown-toggle" data-toggle="dropdown" aria-expanded="false">

<i class="fa fa-ellipsis-horizontal"></i> 操作 <i class="fa fa-angle-down"></i>

</button>

<ul class="dropdown-menu pull-right">

<li>

<!-- #region if-->

<!-- ko if:item.StatusId()==1 -->

<a href="javascript:;" data-bind="click:function(){$parent.eventOffShelf(item.Id())}">

下架

</a>

<!--/ko-->

<!-- #endregion endif-->

<!-- #region if -->

<!-- ko if:item.StatusId()!=1 -->

<a href="javascript:;" data-bind="click:function(){$parent.eventOnShelf(item.Id())}">

上架

</a>

<!--/ko-->

<!-- #endregion endif-->

</li>

<li>

<a href="javascript:;" data-bind="attr:{href:formatUrl('/PackageConfig/Edit?packageId='+item.Id()+'&sitemap='+encodeURI('套餐研发~套餐设计器~编辑套餐'))}">

编辑

</a>

</li>

<li>

<a href="javascript:;" data-bind="click:function(){$parent.eventShowCopy(item)}">

复制

</a>

</li>

<!-- #region if -->

<!-- ko if:item.StatusId()==0-->

<li>

<a href="javascript:;" data-bind="click:function(){$parent.eventDelPackage(item.Id())}">

删除

</a>

</li>

<!--/ko-->

<!-- #endregion endif-->

<li>

<a data-bind="attr:{href:formatUrl('/PackageConfig/TagManage?packageId='+item.Id()+'&sitemap='+encodeURI('套餐研发~套餐设计器~标签管理'))}">

标签管理

</a>

</li>

<li>

<a href="javascript:;" data-bind="click:function(){$parent.eventShowRelation(item.Id(),item.Name())}">

套餐关联

</a>

</li>

</ul>

</div>

</td>

</tr>

</tbody>

</table>

</div>

<pagingcontrol params="{paging:listPackage}"></pagingcontrol>

 

环境搭建

依赖包:

Install-Package jquery.TypeScript.DefinitelyTyped -Version 1.9.9

Install-Package jQuery -Version 1.11.1

Install-Package linq.TypeScript.DefinitelyTyped

Install-Package linq.js -Version 2.2.0.2

Install-Package knockout.TypeScript.DefinitelyTyped

Install-Package knockoutjs -Version 3.4.0

依赖插件:

vs2015及以上*

https://www.tslang.cn/index.html#download-links * 下载 对应版本

http://www.vswebessentials.com/ (常用工具)

resharper 10 以上(智能提示)

WebCompiler(less编译)

问题汇总

  1. 如果是 固定标签之内的组件绑定 需要使用无容器方式,不能使用自定义标签方式。

  2. 组件绑定时在params上传递的值必须是监听属性(也就是说productId,切记不能 使用productId())这样做在productId更新时,组件会被重置。

    如果有一天你跟断点事遇到一个component被无限初始化的时候,先看一下是不是传入参数有问题。

  3. 组件支持套用,可以组件套组件,今天遇到了一个情况(外层组件被初始化2次,而内部组件初始化不走),后来仔细检查,发现内层组件的register时传入的viewModel是外层组件的,这是个低级错误。

    写组件时不要忘记检查 组件名,viewModel和模板路径。

更新日志

2017-04-06 09:02:43 添加环境搭建教程,添加项目问题汇总,添加更新日志记录。

2017-04-19 10:57:17 问题汇总更新了2个问题。WebUtil添加事件总线机制,component之间的交互在也不用来回传递事件了。

posted on 2017-04-19 10:59  shanheblog  阅读(272)  评论(1编辑  收藏  举报

导航