作為“貼程式碼”力推的一個CRUD實踐專案PasteTemplate,在對現有的3個專案進行實戰後效果非常舒服!下面就針對PasteForm為啥我願稱為最佳CRUD做一些回答:
哪裡可以下載這個PasteForm的專案案例
目前“貼程式碼”對外使用PasteForm的專案有"貼Builder(PasteSpider)"和案例專案(PasteTemplate),其中案例專案你可以在
https://gitee.com/pastecode/paste-template 獲得
PasteForm的CRUD模式有什麼特色?
搞這個的起初是因為我有一個專案要在小程式上行實現大量的表單,思考了下,一大堆相似的程式碼,不是可以使用那個元件的方式實現麼,那就需要傳入模型,換位思考下,那麼我們經常使用的CRUD是否也可以這麼搞?
於是就有了這個利用多資料模型Dto和反射的原理實現的PasteForm,主要優點如下
1.PasteForm輸出的是一個思想,就是管理端的頁面由後端控制,無論是安全性還是資料的表現
2.前端編寫一次後,後續都不用編寫,如果你使用的是我的案例專案,那麼你前端都可以不用編寫,包括後端要新增表,修改欄位等,前端都不再需要修改程式碼
3.前端頁面的超簡潔性,無論你的後端是30個表還是100個表,對於前端來說都差不多大概4個頁面搞定!當然了一些特殊的需求,還是需要編寫下的!
4.無語言限制,上面說得輸出的是一個思想,本文的案例使用的是.netcore+html的形式實現的,你也可以使用比如java+vue的模式
5.統一性,比如你30個表的圖片上傳,在表單中他們的樣式都是一樣的,因為他們其實使用的是同一個管理端頁面
6.後端統籌所有,比如欄位的顯示順序,欄位是否顯示,欄位的預設值等等等,都由後端對應的Dto來限制和控制,比如同樣的新增,由於當前登陸使用者的角色不同,你可以限制某一個欄位張三必填,而李四可以是選填!
看到管理端的程式碼,感覺像JSP的?
整個PasteForm的原理就是後端把資料模型的屬性,欄位等返回給前端,前端基於後端返回的資料再渲染到UI上,為了便於編寫所以採用了template的模式,其實不是JSP,是長得像而已!
我的專案擁有120個資料表,那是不是要編寫很多的後端檔案?
PasteForm原則上只有3個頁面pasteform/index.html,pasteform/view.html,pasteform/detail.html,只是有一些特殊情況下,會新增一些頁面,這個要看你專案的實際需求,其實無論多少個表,PasteForm的頁面都只是那麼幾個,比如說許可權的頁面為pasteform/index.html?path=roleInfo而使用者的表為pasteform/index.html?path=userInfo,你會發覺其實他們是同一個頁面,只是引數path不一樣而已,這個path就是WebApi的Service!
其實你完全可以一個頁面都不寫,直接使用我提供的案例專案PasteTemplate中的pasteform的檔案!
我看案例PasteTemplate專案是使用html,js,css編寫的,可以使用vue編寫管理端麼?
上面提到的,PasteForm主要輸出的是一個思想,也就是由後端的Dto控制前端的頁面呈現和互動等,所以從WebApi中請求得到模板得屬性欄位等資訊後,你可以使用其他語言實現管理端得頁面得,不限原生,vue,angular等
pasteform/index.html是幹嘛用?
作為對應資料表得資料展示用,一般為表格table呈現,對應模型為XXXListDto,也包括了搜尋區域,搜尋區域由對應得InputQueryXXX的資料模型決定,一般只有page,size,word三個欄位!當然了這個頁面也包含了新增資料,編輯,詳情,刪除等的操作互動!
pasteform/view.html是做什麼用得?
新增資料,或者編輯資料的時候使用的都是這個頁面,不過他們讀取的資料模型不一樣,比如許可權這個表roleInfo,則有新增的時候為pasteform/view.html?path=roleInfo,對應的資料模型為roleInfoAddDto,而在編輯的時候為pasteform/view.html?path=roleInfo&id=3,對應的資料模型為roleInfoUpdateDto。頁面的整體邏輯就是從WebApi中獲得對應模型後,基於規則渲染到UI中,包括預設值,當前值等,提交的時候再從頁面中的form讀取到值提交給後端,所以說主要的控制還是在後端中!
pasteform/detail.html是做什麼用得?
有些時候我們的資料沒辦法在pasteform/index.html頁面中顯示完成,比如文章列表,主要是一些欄位過長的,這個時候我們希望有一個頁面顯示詳細內容,所以detail就是幹這個用的,對應的資料模型為XXXDto!
我不會.netcore可以用其他語言寫pasteform麼
PasteForm輸出的是一個思想,所以使用其他語言也是可以的,不過你得對著寫一整套的就是了,比如你可以使用vue+java的模式搞一套!後端核心程式碼案例
/// <summary>
/// 讀取AddDto的資料模型
/// </summary>
/// <returns></returns>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "root", "root" })]
public PasteBuilderHelper.VoloModelInfo ReadAddModel()
{
var _model = PasteBuilderHelper.ReadModelProperty<RoleInfoAddDto>(new RoleInfoAddDto());
return _model;
}
/// <summary>
/// 讀取UpdateDto的資料模型
/// </summary>
/// <returns></returns>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "root", "root" })]
public async Task<PasteBuilderHelper.VoloModelInfo> ReadUpdateModel(int id)
{
var _query = from a in _dbContext.RoleInfo
join b in _dbContext.RoleInfo on a.FatherId equals b.Id into c
from rol in c.DefaultIfEmpty()
select new RoleInfoUpdateDto
{
Id = a.Id,
Desc = a.Desc,
FatherId = a.FatherId,
Icon = a.Icon,
IsEnable = a.IsEnable,
Model = a.Model,
Name = a.Name,
Path = a.Path,
Role = a.Role,
RoleType = a.RoleType,
Sort = a.Sort,
ExtendRole = rol != null ? new RoleShortModel
{
Id = rol.Id,
Model = rol.Model,
Name = rol.Name,
Path = rol.Path,
Role = rol.Role,
RoleType = rol.RoleType
} : null
};
var _info = await _query.Where(x => x.Id == id).AsNoTracking().FirstOrDefaultAsync();
if (_info == null || _info == default)
{
throw new PasteCodeException("查詢的資訊不存在,無法執行編輯操作!");
}
var _dataModel = PasteBuilderHelper.ReadModelProperty<RoleInfoUpdateDto>(_info);
return _dataModel;
}
/// <summary>
/// 讀取UpdateDto的資料模型
/// </summary>
/// <returns></returns>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "root", "root" })]
public async Task<PasteBuilderHelper.VoloModelInfo> ReadDetailModel(int id)
{
var _query = from a in _dbContext.RoleInfo
join b in _dbContext.RoleInfo on a.FatherId equals b.Id into c
from rol in c.DefaultIfEmpty()
select new RoleInfoDto
{
Id = a.Id,
Desc = a.Desc,
FatherId = a.FatherId,
Icon = a.Icon,
IsEnable = a.IsEnable,
Model = a.Model,
Name = a.Name,
Path = a.Path,
Role = a.Role,
RoleType = a.RoleType,
SortStr = a.SortStr,
FatherStr = a.FatherStr,
Level = a.Level,
RootId = a.RootId,
Sort = a.Sort,
ExtendRole = rol != null ? new RoleShortModel
{
Id = rol.Id,
Model = rol.Model,
Name = rol.Name,
Path = rol.Path,
Role = rol.Role,
RoleType = rol.RoleType
} : null
};
var _info = await _query.Where(x => x.Id == id).AsNoTracking().FirstOrDefaultAsync();
if (_info == null || _info == default)
{
throw new PasteCodeException("查詢的資訊不存在,無法執行編輯操作!");
}
var _dataModel = PasteBuilderHelper.ReadModelProperty<RoleInfoDto>(_info);
return _dataModel;
}
/// <summary>
/// 讀取ListDto的資料模型
/// </summary>
/// <returns></returns>
[HttpGet]
[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "root", "root" })]
public PasteBuilderHelper.VoloModelInfo ReadListModel()
{
var _model = PasteBuilderHelper.ReadModelProperty<RoleInfoListDto>(new RoleInfoListDto());
var _query_model = PasteBuilderHelper.ReadModelProperty(new InputQueryRoleInfo());
if (_query_model != null)
{
_model.QueryProperties = _query_model.Properties;
}
return _model;
}
PasteForm使用到的各個模型是幹嘛用的,Dto
我最早接觸Dto的時候是ABPvNext,說到這個Dto不得不說ObjectMapper,比如說許可權表RoleInfo,則對應的有RoleInfoAddDto,RoleInfoUpdateDto,RoleInfoDto和RoleInfoListDto,當前實際開發中你可能還會引申出更多的Dto,比如我常用的RoleMenuDto,RoleAuthDto等,PasteForm中主要用到前面的4個,一般還會附帶一個InputQueryRoleInfo!比如說新增的時候,使用者提交的資料模型是RoleInfoAddDto,提交給API後,API處資料校驗合法後再使用AutoMapper把RoleInfoAddDto轉化成RoleInfo,然後寫入到資料庫!
XXXAddDto:用於新增的時候的資料模型,對應的是pasteform/view.html頁面使用
XXXUpdateDto:用於資料編輯修改的資料模型,對應的是pasteform/view.html頁面使用
XXXDto:這裡我一般用於顯示詳情的時候的資料模型,也就是pasteform/detail.html的頁面使用
XXXListDto:這裡一般使用於資料表格展示的時候的資料模型,也就是pasteform/index.html的頁面使用
InputQueryXXX:這裡一般用於表格上方的搜尋項的資料模型,也就是pasteform/index.html的搜尋區域使用
如何上傳圖片?
圖片分新增編輯和展示,展示的話這裡就是pasteform/index.html和pasteform/detail.html頁面中了,其實在編輯的時候pasteform/view.html頁面上也有展示,只要給對應欄位新增屬性[ColumnDataTypeAttribute("image","1","image","60*60")]即可
/// <summary>
/// 多圖 回傳的值是多個的,使用,隔開
/// </summary>
[ColumnDataType("image", "3", "img", "60*60")]
public string Img2 { get; set; }
/// <summary>
/// 圖片 回傳的使用string[]的模式
/// </summary>
[ColumnDataType("image", "3", "img", "60*60")]
public string[] Img3 { get; set; }
表格中如何讓資料左靠
///<summary>
///文字區域 模擬文字區域的輸入
///</summary>
[ColumnDataType("class","fleft")]
public string Desc { get; set; }
表格中如何自定義顯示
///<summary>
///單選 一般表示狀態,內定的,有點像Enum,關於Enum後續會支援
///</summary>
[ColumnDataType("html", "<div>{{:=item.dateType}}-{{:=item.gradeId}}</div>")]
public int DateType { get; set; }
表格中如何顯示按鈕
/// <summary>
/// 普通選單
/// </summary>
[ColumnDataType("menu", "選單一", "open_window('查閱使用者帶參','./index.html?path=userInfo&xxid={{:=item.id}}');", "Hui-iconfont-menu")]
public string Menu2 { get; set; }
表格中如何顯示條件按鈕
有些時候我們需要基於當前行資料進行判斷,是否顯示某一個按鈕,則有
/// <summary>
/// 普通條件選單
/// </summary>
[ColumnDataType("ifmenu", "item.age==7", "<a href=\"javascript:;\" onclick=\"open_window(`111`,`./index.html?path=userInfo&goid={{:=item.id}}`)\">條件1</a>", "")]
public string Menu3 { get; set; }
按鈕太多,如何使用按鈕盒子
/// <summary>
/// 選單盒子選單
/// </summary>
[ColumnDataType("menu", "選單二", "open_window('查閱使用者帶參','./index.html?path=userInfo&xxid={{:=item.id}}');", "Hui-iconfont-menu", "box")]
public string Menu5 { get; set; }
/// <summary>
/// 選單盒子中的條件選單
/// </summary>
[ColumnDataType("ifmenu", "item.age==8", "<a href=\"javascript:;\" onclick=\"open_window(`222`,`./index.html?path=userInfo&goid={{:=item.id}}`)\">條件2</a>", "box")]
public string Menu4 { get; set; }
按照排序查下,某些欄位支援升序降序
前端只是基於這個屬性,在查下的時候會回傳orderby欄位給後端,後續需要基於回傳的這個欄位進行orderby查詢
///<summary>
///排序
///</summary>
[ColumnDataType("orderby", "Sort","Sort desc")]
public int Sort { get; set; }
--- 未完待續,下期繼續 ---