如下简称为 kot。kot是一套基于knockout jquery bootstrap(Metronic) typescript 的可快速推广的完整前端框架。 javascript
应用程序启动时自动调用的配置。 css
BundleConfig.cs mvc压缩css和js的配置文件。 html
FilterConfig.cs mvc过滤器配置,站点地图权限登陆菜单管理的入口。 前端
RotuteConfig.cs 路由规则配置。 java
Webstack 文件夹下含有 jquery
ExceptionFilter.cs 网站程序异常处理过滤器。 web
MvcMenuFilter.cs 框架权限验证和菜单处理主要程序。 ajax
WebSiteExcetpion.cs 自定义网站错误类型。用于抛出登信息丢失等自定义业务异常。 typescript
网站主要引用的资源文件(样式,经常使用组件等)。现应用的是metronic_v4.1.0。 json
Admin 管理框架模板
Global bootstrap全局引用资源
Public 网站经常使用资源,如暂无图片和一些补丁css
网站控制器
安装盘古分词的字典,如不用分词搜索则能够删除。
Mvc过滤器文件夹,里面能够自定义一些过滤器,自定义的过滤器须要在App_Start下的FilterConfig.cs中注册。
框架依赖js文件和ts文件。
业务须要调用的用户控件。
Mvc 视图文件夹,母版页在 Shared 为 _Layout.cshtml
其余为业务视图。
定义一些 HttpModule的自定义方法,目前在框架应用中,主要用于处理通用上传和下载。
全局配置入口。
网站通用信息快捷调用方式,好比系统id、当前登陆用户等。
网站配置文件。
经过业务区分业务文件夹。
登陆流程:
一、【统一登陆】像Session中添加登陆信息
二、【网站过滤器验证】 (MvcMenuFilter.cs 须要在FilterConfig中注册)
每个action请求时会判断当前Session中是否有登陆信息。
若是登陆信息为空,判断请求方式是同步请求仍是异步请求。
同步请求,则经过Redrect 跳转到登陆页。
异步请求,则经过throw new WebSiteException ,错误码为1000.
三、异常处理。
全部网站异常都会被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使用
通用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
*/
WebUtil.encodeHtml("<div></div>"); 返回string类型。
结果会把 < 这种尖角号 转换为 < 这种符号。
/**
* 中止冒泡事件
* @param {} event
* @returns {}
* 做者:崔园清
* 小组:山河web
* 说明:中止冒泡事件;
* 建立日期:2014-9-27 14:59:17
* 版本号:v1.0
*/
/**
* 将一个string值转换为时间
* @param {} value
* @returns {}
*/
把一个字符串转换成时间类型。
var str="2015-09-10 18:30"; var date = WebUtil.parseDate(str);
/**
* 获取url的参数
* @param {} name
* @returns {}
*/
获取连接中的QueryString参数。
WebUtil.getQueryString("id"); // 返回 001
WebUtil.getQueryString("name");// 返回 bob
WebUtil.getQueryString("ddd");// 返回 空字符串""
/**
* ajax
* @param option
* @returns {}
*/
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 {}
*/
一个在分页状态下获取连续序号的方法。这里$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 {}
*/
普通提示框: title 默认为 "系统提示"
1)sh.alert("操做成功!");
2)sh.alert("操做完成!",function(){console.log("数据操做完成") });
3)sh.alert("操做完成!",function(){console.log("数据操做完成") },"DaTree提示");
包含肯定取消按钮的提示框: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 {}
*/
1)通用注册控件方法
//注册控件
ko.RegisterControl("priceconfigcontrol", PriceConfigControlViewModel, formatUrl("/UserControls/PackageConfig/PriceConfigControl/PriceConfigControlView.html"));
2)registerControl方法会形成不少次异步html请求,正在想办法解决。
/**
* 将数字转换为 格式化后的金钱字符串
* @param num
*/
declare function formatCurrency(num: number): string;
<div class="col-md-3 ">
<p class="form-control-static">
套餐必选成本合计:<!--ko text:formatCurrency(priceConfigModel().RequiredCostTotalPrice())--><!--/ko-->元
</p>
</div>
全局处理虚拟目录的方法。在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接口
*/
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静态方法
*/
interface StringConstructor {
format: (...args: any[]) => string;
}
这里与C#中静态调用稍有不一样。
C#中 : string str=string.Format("今天是{0}国庆节","10月1号");
js中: var str="今天是{0}国庆节".format("10月1号");
简单例子: 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;
}
/**
* 为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;
}
通常用于字典类型的数据处理
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;
/**
* 事件总线接口
*/
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", "标题", "消息");
显示等待框方法
/**
* 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");
/**
* 弹窗组件参数
*/
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>
*/
/// <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;
}
通用上传控件
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;
})()
});
经过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> <a href="javascript:;"> ${$data}</a> </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>
框架 外部引用非必须组件
能够在js中作 lambda 查询
具体用法请 linq.d.ts;
Enumerable.From(this.SpaceSchemesItems()).Where(x => x.IsDefault()).Sum(x => x.TotalCostPrice());
this.DefaultCount = ko.computed<number>(() => {
return <number>Enumerable.From(this.SpaceSchemesItems()).Count(d => d.IsDefault());
}, this);
下面附上标准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>
1)属性与构造相对应:ts中声明的属性是抽象的,须要在构造中实例化。
class PackageListModel {
/**
* 空间类型集合
*/
SpaceTypes: KnockoutObservableArray<string>;
constructor(model?: any) {
this.SpaceTypes=ko.observableArray([]);
}
2)this做用域:没法区分this做用域时。
var self=this;
html书写规范遵循语义化的标准写法
好比声明一个按钮<button class="btn btn-default">肯定</button>
尽可能不要写 <a href="javascript:;" class="btn btn-default">肯定</a>
1) 容器布局:遵循bootstrap的标签套用原则,不该有多余标签。
全部内容都应放在row下的col里。
2) 标签页:在Metronic的布局标准下,标签页的容器应为row和col。
代码详见Metronic模版。
3) 页面html行数较多时要添加region标签。Ctrl+k,s
4) Layout布局容器应在.container 里
5)搜索框应用panel包裹
标签 |
命名 |
<input type="text" /> |
txtName |
<select></select> |
selProjectState |
<texarta></texarea> |
textProductDesc |
<label></lable> |
lbPrice |
<div></div> |
divProjectFile |
<span></span> |
spSKUPro |
标签页 |
tabUserManage |
模态框 |
modalAddUser |
遮罩层 |
dialogLoading |
2)Bussiness调用服务接口,简单逻辑处理,模型转换等。
组件 在项目中被命名为UserControl
如下是通用注册方法,在webutil中声明。
Demo见viewModel。
下图是一个ts版的demo
viewModel 由4部分组成
属性上都应有注释,若是有依赖关系 能够用 region 扩起来。
下面是方法的例子,这些方法都用于数据交互。(ts中不写返回类型默认为 void)
事件必定要注明用途,控件回调的事件也在这个区域声明。
下面有一个事件叫 eventShowRelation 显示关联套餐弹窗,其中有一行代码,this.modalRelationPackage.show();
这行代码 调用的就是弹窗控件的方法(详见,js控件下的 jquery.sh.popups.js的api)。
控件回调事件写法
控件初始化绑定方法方法。
Knockout的组件能够实现自定义html标签的功能。
用于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编译)
若是有一天你跟断点事遇到一个component被无限初始化的时候,先看一下是否是传入参数有问题。
写组件时不要忘记检查 组件名,viewModel和模板路径。
2017-04-06 09:02:43 添加环境搭建教程,添加项目问题汇总,添加更新日志记录。
2017-04-19 10:57:17 问题汇总更新了2个问题。WebUtil添加事件总线机制,component之间的交互在也不用来回传递事件了。