如下簡稱爲 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之間的交互在也不用來回傳遞事件了。