標籤: 網頁設計公司

  • 基於領域驅動設計(DDD)超輕量級快速開發架構,ASP.NET MVC 5 SmartCode Scaffolding for Visual Studio.Net

    基於領域驅動設計(DDD)超輕量級快速開發架構,ASP.NET MVC 5 SmartCode Scaffolding for Visual Studio.Net

     

    smartadmin.core.urf 這個項目是基於asp.net core 3.1(最新)基礎上參照領域驅動設計(DDD)的理念,並參考目前最為了流行的abp架構開發的一套輕量級的快速開發web application 技術架構,專註業務核心需求,減少重複代碼,開始構建和發布,讓初級程序員也能開發出專業並且漂亮的Web應用程序

    域驅動設計(DDD)是一種通過將實現與不斷髮展的模型相連接來滿足複雜需求的軟件開發方法。域驅動設計的前提如下:

    • 將項目的主要重點放在核心領域和領域邏輯上;
    • 將複雜的設計基於領域模型;
    • 啟動技術專家和領域專家之間的創造性合作,以迭代方式完善解決特定領域問題的概念模型。

    最終的核心思想還是SOLID,只是實現的方式有所不同,ABP可能目前對DDD設計理念最好的實現方式。但對於小項目我還是更喜歡 URF.Core https://github.com/urfnet/URF.Core 這個超輕量級的實現。

    同時這個項目也就是我2年前的一個開源項目 ASP.NET MVC 5 SmartCode Scaffolding for Visual Studio.Net 的升級版,支持.net core.目前沒有把所有功能都遷移到.net core,其中最重要的就是代碼生成這塊。再接下來的時間里主要就是完善代碼生成的插件。當然也要看是否受歡迎,如果反應一般,我可能不會繼續更新。

    Demo 網站

     演示站點 
    賬號:demo 密碼:123456

    GitHub 源代碼 https://github.com/neozhu/smartadmin.core.urf

    喜歡請給個 Star 每一顆Star都是鼓勵我繼續更新的動力 謝謝
    如果你用於自己公司及盈利性的項目,希望給與金錢上的贊助,並且保留原作者的版權

    分層

    smartadmin.core.urf遵行DDD設計模式來實現應用程序的四層模型

    • 表示層(Presentation Layer):用戶操作展示界面,使用SmartAdmin – Responsive WebApp模板+Jquery EasyUI
    • 應用層(Application Layer):在表示層與域層之間,實現具體應用程序邏輯,業務用例,Project:StartAdmin.Service.csproj
    • 域層(Domain Layer):包括業務對象(Entity)和核心(域)業務規則,應用程序的核心,使用EntityFrmework Core Code-first + Repository實現
    • 基礎結構層(Infrastructure Layer):提供通用技術功能,這些功能主要有第三方庫來支持,比如日誌:Nlog,服務發現:Swagger UI,事件總線(EventBus):dotnetcore/CAP,認證與授權:Microsoft.AspNetCore.Identity,後面會具體介紹

    內容

     

    域層(Domain Layer)

    • 實體(Entity,BaseEntity) 通常實體就是映射到關係數據庫中的表,這裏說名一下最佳做法和慣例:
    1. 在域層定義:本項目就是(SmartAdmin.Entity.csproj)
    2. 繼承一個基類 Entity,添加必要審計類比如:創建時間,最後修改時間等
    3. 必須要有一個主鍵最好是GRUID(不推薦複合主鍵),但本項目使用遞增的int類型
    4. 字段不要過多的冗餘,可以通過定義關聯關係
    5. 字段屬性和方法盡量使用virtual關鍵字。有些ORM和動態代理工具需要

     

    • 存儲庫(Repositories) 封裝基本數據操作方法(CRUD),本項目應用 URF.Core實現
    • 域服務
    • 技術指標
    • 應用層

      • 應用服務:用於實現應用程序的用例。它們用於將域邏輯公開給表示層,從表示層(可選)使用DTO(數據傳輸對象)作為參數調用應用程序服務。它使用域對象執行某些特定的業務邏輯,並(可選)將DTO返回到表示層。因此,表示層與域層完全隔離。對應本項目:(SmartAdmin.Service.csproj)
      • 數據傳輸對象(DTO):用於在應用程序層和表示層或其他類型的客戶端之間傳輸數據,通常,使用DTO作為參數從表示層(可選)調用應用程序服務。它使用域對象執行某些特定的業務邏輯,並(可選)將DTO返回到表示層。因此,表示層與域層完全隔離.對應本項目:(SmartAdmin.Dto.csproj)
      • Unit of work:管理和控制應用程序中操作數據庫連接和事務 ,本項目使用 URF.Core實現
    • 基礎服務層

      • UI樣式定義:根據用戶喜好選擇多種頁面显示模式
      • 租戶管理:使用EntityFrmework Core提供的Global Filter實現簡單多租戶應用
      • 賬號管理: 對登錄系統賬號維護,註冊,註銷,鎖定,解鎖,重置密碼,導入、導出等功能
      • 角色管理:使用Microsoft身份庫管理角色,用戶及其權限管理
      • 導航菜單:系統主導航欄配置
      • 角色授權:配置角色显示的菜單
      • 鍵值對配置:常用的數據字典維護,如何正確使用和想法後面會介紹
      • 導入&導出配置:使用Excel導入導出做一個可配置的功能
      • 系統日誌:asp.net core 自帶的日誌+Nlog把所有日誌保存到數據庫方便查詢和分析
      • 消息訂閱:集中訂閱CAP分佈式事件總線的消息
      • WebApi: Swagger UI Api服務發現和在線調試工具
      • CAP: CAP看板查看發布和訂閱的消息

    快速上手開發

    • 開發環境
      • Visual Studio .Net 2019
      • .Net Core 3.1
      • Sql Server(LocalDb)
    • 附加數據庫

      使用SQL Server Management Studio 附加.\src\SmartAdmin.Data\db\smartadmindb.mdf 數據庫(如果是localdb,那麼不需要修改數據庫連接配置)

    • 打開解決方案

    第一個簡單的需求開始 
    新增 Company 企業信息 完成CRUD 導入導出功能

    • 新建實體對象(Entity)

    在SmartAdmin.Entity.csproj項目的Models目錄下新增一個Company.cs類

     1 //記住:定義實體對象最佳做法,繼承基類,使用virtual關鍵字,盡可能的定義每個屬性,名稱,類型,長度,校驗規則,索引,默認值等
     2 namespace SmartAdmin.Data.Models
     3 {
     4     public partial class Company : URF.Core.EF.Trackable.Entity
     5     {
     6         [Display(Name = "企業名稱", Description = "歸屬企業名稱")]
     7         [MaxLength(50)]
     8         [Required]
     9         //[Index(IsUnique = true)]
    10         public virtual string Name { get; set; }
    11         [Display(Name = "組織代碼", Description = "組織代碼")]
    12         [MaxLength(12)]
    13         //[Index(IsUnique = true)]
    14         [Required]
    15         public virtual string Code { get; set; }
    16         [Display(Name = "地址", Description = "地址")]
    17         [MaxLength(128)]
    18         [DefaultValue("-")]
    19         public virtual string Address { get; set; }
    20         [Display(Name = "聯繫人", Description = "聯繫人")]
    21         [MaxLength(12)]
    22         public virtual string Contect { get; set; }
    23         [Display(Name = "聯繫電話", Description = "聯繫電話")]
    24         [MaxLength(20)]
    25         public virtual string PhoneNumber { get; set; }
    26         [Display(Name = "註冊日期", Description = "註冊日期")]
    27         [DefaultValue("now")]
    28         public virtual  DateTime RegisterDate { get; set; }
    29     }
    30 }
    31 //在 SmartAdmin.Data.csproj 項目 SmartDbContext.cs 添加
    32 public virtual DbSet<Company> Companies { get; set; }

    View Code

    • 添加服務對象 Service

    在項目 SmartAdmin.Service.csproj 中添加ICompanyService.cs,CompanyService.cs 就是用來實現業務需求 用例的地方

      1 //ICompany.cs
      2 //根據實際業務用例來創建方法,默認的CRUD,增刪改查不需要再定義
      3 namespace SmartAdmin.Service
      4 {
      5   // Example: extending IService<TEntity> and/or ITrackableRepository<TEntity>, scope: ICustomerService
      6   public interface ICompanyService : IService<Company>
      7   {
      8     // Example: adding synchronous Single method, scope: ICustomerService
      9     Company Single(Expression<Func<Company, bool>> predicate);
     10     Task ImportDataTableAsync(DataTable datatable);
     11     Task<Stream> ExportExcelAsync(string filterRules = "", string sort = "Id", string order = "asc");
     12   }
     13 }
     14 // 具體實現接口的方法
     15 namespace SmartAdmin.Service
     16 {
     17   public class CompanyService : Service<Company>, ICompanyService
     18   {
     19     private readonly IDataTableImportMappingService mappingservice;
     20     private readonly ILogger<CompanyService> logger;
     21     public CompanyService(
     22       IDataTableImportMappingService mappingservice,
     23       ILogger<CompanyService> logger,
     24       ITrackableRepository<Company> repository) : base(repository)
     25     {
     26       this.mappingservice = mappingservice;
     27       this.logger = logger;
     28     }
     29 
     30     public async Task<Stream> ExportExcelAsync(string filterRules = "", string sort = "Id", string order = "asc")
     31     {
     32       var filters = PredicateBuilder.FromFilter<Company>(filterRules);
     33       var expcolopts = await this.mappingservice.Queryable()
     34              .Where(x => x.EntitySetName == "Company")
     35              .Select(x => new ExpColumnOpts()
     36              {
     37                EntitySetName = x.EntitySetName,
     38                FieldName = x.FieldName,
     39                IgnoredColumn = x.IgnoredColumn,
     40                SourceFieldName = x.SourceFieldName
     41              }).ToArrayAsync();
     42 
     43       var works = (await this.Query(filters).OrderBy(n => n.OrderBy(sort, order)).SelectAsync()).ToList();
     44       var datarows = works.Select(n => new
     45       {
     46         Id = n.Id,
     47         Name = n.Name,
     48         Code = n.Code,
     49         Address = n.Address,
     50         Contect = n.Contect,
     51         PhoneNumber = n.PhoneNumber,
     52         RegisterDate = n.RegisterDate.ToString("yyyy-MM-dd HH:mm:ss")
     53       }).ToList();
     54       return await NPOIHelper.ExportExcelAsync("Company", datarows, expcolopts);
     55     }
     56 
     57     public async Task ImportDataTableAsync(DataTable datatable)
     58     {
     59       var mapping = await this.mappingservice.Queryable()
     60                         .Where(x => x.EntitySetName == "Company" &&
     61                            (x.IsEnabled == true || (x.IsEnabled == false && x.DefaultValue != null))
     62                            ).ToListAsync();
     63       if (mapping.Count == 0)
     64       {
     65         throw new  NullReferenceException("沒有找到Work對象的Excel導入配置信息,請執行[系統管理/Excel導入配置]");
     66       }
     67       foreach (DataRow row in datatable.Rows)
     68       {
     69 
     70         var requiredfield = mapping.Where(x => x.IsRequired == true && x.IsEnabled == true && x.DefaultValue == null).FirstOrDefault()?.SourceFieldName;
     71         if (requiredfield != null || !row.IsNull(requiredfield))
     72         {
     73           var item = new Company();
     74           foreach (var field in mapping)
     75           {
     76             var defval = field.DefaultValue;
     77             var contain = datatable.Columns.Contains(field.SourceFieldName ?? "");
     78             if (contain && !row.IsNull(field.SourceFieldName))
     79             {
     80               var worktype = item.GetType();
     81               var propertyInfo = worktype.GetProperty(field.FieldName);
     82               var safetype = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
     83               var safeValue = (row[field.SourceFieldName] == null) ? null : Convert.ChangeType(row[field.SourceFieldName], safetype);
     84               propertyInfo.SetValue(item, safeValue, null);
     85             }
     86             else if (!string.IsNullOrEmpty(defval))
     87             {
     88               var worktype = item.GetType();
     89               var propertyInfo = worktype.GetProperty(field.FieldName);
     90               if (string.Equals(defval, "now", StringComparison.OrdinalIgnoreCase) && (propertyInfo.PropertyType == typeof(DateTime) || propertyInfo.PropertyType == typeof(Nullable<DateTime>)))
     91               {
     92                 var safetype = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
     93                 var safeValue = Convert.ChangeType(DateTime.Now, safetype);
     94                 propertyInfo.SetValue(item, safeValue, null);
     95               }
     96               else if (string.Equals(defval, "guid", StringComparison.OrdinalIgnoreCase))
     97               {
     98                 propertyInfo.SetValue(item, Guid.NewGuid().ToString(), null);
     99               }
    100               else if (string.Equals(defval, "user", StringComparison.OrdinalIgnoreCase))
    101               {
    102                 propertyInfo.SetValue(item, "", null);
    103               }
    104               else
    105               {
    106                 var safetype = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
    107                 var safeValue = Convert.ChangeType(defval, safetype);
    108                 propertyInfo.SetValue(item, safeValue, null);
    109               }
    110             }
    111           }
    112           this.Insert(item);
    113         }
    114       }
    115     }
    116 
    117     // Example, adding synchronous Single method
    118     public Company Single(Expression<Func<Company, bool>> predicate)
    119     {
    120       
    121       return this.Repository.Queryable().Single(predicate);
    122 
    123     }
    124   }
    125 }

    View Code

    • 添加Controller

    MVC Controller

      1 namespace SmartAdmin.WebUI.Controllers
      2 {
      3   public class CompaniesController : Controller
      4   {
      5     private  readonly ICompanyService companyService;
      6     private readonly IUnitOfWork unitOfWork;
      7     private readonly ILogger<CompaniesController> _logger;
      8     private readonly IWebHostEnvironment _webHostEnvironment;
      9     public CompaniesController(ICompanyService companyService,
     10           IUnitOfWork unitOfWork,
     11           IWebHostEnvironment webHostEnvironment,
     12           ILogger<CompaniesController> logger)
     13     {
     14       this.companyService = companyService;
     15       this.unitOfWork = unitOfWork;
     16       this._logger = logger;
     17       this._webHostEnvironment = webHostEnvironment;
     18     }
     19 
     20     // GET: Companies
     21     public IActionResult Index()=> View();
     22     //datagrid 數據源
     23     public async Task<JsonResult> GetData(int page = 1, int rows = 10, string sort = "Id", string order = "asc", string filterRules = "")
     24     {
     25       try
     26       {
     27         var filters = PredicateBuilder.FromFilter<Company>(filterRules);
     28         var total = await this.companyService
     29                              .Query(filters)
     30                              .AsNoTracking()
     31                              .CountAsync()
     32                               ;
     33         var pagerows = (await this.companyService
     34                              .Query(filters)
     35                               .AsNoTracking()
     36                            .OrderBy(n => n.OrderBy(sort, order))
     37                            .Skip(page - 1).Take(rows)
     38                            .SelectAsync())
     39                            .Select(n => new
     40                            {
     41                              Id = n.Id,
     42                              Name = n.Name,
     43                              Code = n.Code,
     44                              Address = n.Address,
     45                              Contect = n.Contect,
     46                              PhoneNumber = n.PhoneNumber,
     47                              RegisterDate = n.RegisterDate.ToString("yyyy-MM-dd HH:mm:ss")
     48                            }).ToList();
     49         var pagelist = new { total = total, rows = pagerows };
     50         return Json(pagelist);
     51       }
     52       catch(Exception e) {
     53         throw e;
     54         }
     55 
     56     }
     57     //編輯 
     58     [HttpPost]
     59     [ValidateAntiForgeryToken]
     60     public async Task<JsonResult> Edit(Company company)
     61     {
     62       if (ModelState.IsValid)
     63       {
     64         try
     65         {
     66           this.companyService.Update(company);
     67 
     68           var result = await this.unitOfWork.SaveChangesAsync();
     69           return Json(new { success = true, result = result });
     70         }
     71          catch (Exception e)
     72         {
     73           return Json(new { success = false, err = e.GetBaseException().Message });
     74         }
     75       }
     76       else
     77       {
     78         var modelStateErrors = string.Join(",", this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors.Select(n => n.ErrorMessage)));
     79         return Json(new { success = false, err = modelStateErrors });
     80         //DisplayErrorMessage(modelStateErrors);
     81       }
     82       //return View(work);
     83     }
     84     //新建
     85     [HttpPost]
     86     [ValidateAntiForgeryToken]
     87    
     88     public async Task<JsonResult> Create([Bind("Name,Code,Address,Contect,PhoneNumber,RegisterDate")] Company company)
     89     {
     90       if (ModelState.IsValid)
     91       {
     92         try
     93         {
     94           this.companyService.Insert(company);
     95        await this.unitOfWork.SaveChangesAsync();
     96           return Json(new { success = true});
     97         }
     98         catch (Exception e)
     99         {
    100           return Json(new { success = false, err = e.GetBaseException().Message });
    101         }
    102 
    103         //DisplaySuccessMessage("Has update a Work record");
    104         //return RedirectToAction("Index");
    105       }
    106       else
    107        {
    108         var modelStateErrors = string.Join(",", this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors.Select(n => n.ErrorMessage)));
    109         return Json(new { success = false, err = modelStateErrors });
    110         //DisplayErrorMessage(modelStateErrors);
    111       }
    112       //return View(work);
    113     }
    114     //刪除當前記錄
    115     //GET: Companies/Delete/:id
    116     [HttpGet]
    117     public async Task<JsonResult> Delete(int id)
    118     {
    119       try
    120       {
    121         await this.companyService.DeleteAsync(id);
    122         await this.unitOfWork.SaveChangesAsync();
    123         return Json(new { success = true });
    124       }
    125      
    126       catch (Exception e)
    127       {
    128         return Json(new { success = false, err = e.GetBaseException().Message });
    129       }
    130     }
    131     //刪除選中的記錄
    132     [HttpPost]
    133     public async Task<JsonResult> DeleteChecked(int[] id)
    134     {
    135       try
    136       {
    137         foreach (var key in id)
    138         {
    139           await this.companyService.DeleteAsync(key);
    140         }
    141         await this.unitOfWork.SaveChangesAsync();
    142         return Json(new { success = true });
    143       }
    144       catch (Exception e)
    145       {
    146         return Json(new { success = false, err = e.GetBaseException().Message });
    147       }
    148     }
    149     //保存datagrid編輯的數據
    150     [HttpPost]
    151     public async Task<JsonResult> AcceptChanges(Company[] companies)
    152     {
    153       if (ModelState.IsValid)
    154       {
    155         try
    156         {
    157           foreach (var item in companies)
    158           {
    159             this.companyService.ApplyChanges(item);
    160           }
    161           var result = await this.unitOfWork.SaveChangesAsync();
    162           return Json(new { success = true, result });
    163         }
    164         catch (Exception e)
    165         {
    166           return Json(new { success = false, err = e.GetBaseException().Message });
    167         }
    168       }
    169       else
    170       {
    171         var modelStateErrors = string.Join(",", ModelState.Keys.SelectMany(key => ModelState[key].Errors.Select(n => n.ErrorMessage)));
    172         return Json(new { success = false, err = modelStateErrors });
    173       }
    174 
    175     }
    176     //導出Excel
    177     [HttpPost]
    178     public async Task<IActionResult> ExportExcel(string filterRules = "", string sort = "Id", string order = "asc")
    179     {
    180       var fileName = "compnay" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xlsx";
    181       var stream = await this.companyService.ExportExcelAsync(filterRules, sort, order);
    182       return File(stream, "application/vnd.ms-excel", fileName);
    183     }
    184     //導入excel
    185     [HttpPost]
    186     public async Task<IActionResult> ImportExcel()
    187     {
    188       try
    189       {
    190         var watch = new Stopwatch();
    191         watch.Start();
    192         var total = 0;
    193         if (Request.Form.Files.Count > 0)
    194         {
    195           for (var i = 0; i < this.Request.Form.Files.Count; i++)
    196           {
    197             var model = Request.Form["model"].FirstOrDefault() ?? "company";
    198             var folder = Request.Form["folder"].FirstOrDefault() ?? "company";
    199             var autosave = Convert.ToBoolean(Request.Form["autosave"].FirstOrDefault());
    200             var properties = (Request.Form["properties"].FirstOrDefault()?.Split(','));
    201             var file = Request.Form.Files[i];
    202             var filename = file.FileName;
    203             var contenttype = file.ContentType;
    204             var size = file.Length;
    205             var ext = Path.GetExtension(filename);
    206             var path = Path.Combine(this._webHostEnvironment.ContentRootPath, "UploadFiles", folder);
    207             if (!Directory.Exists(path))
    208             {
    209               Directory.CreateDirectory(path);
    210             }
    211             var datatable = await NPOIHelper.GetDataTableFromExcelAsync(file.OpenReadStream(), ext);
    212             await this.companyService.ImportDataTableAsync(datatable);
    213             await this.unitOfWork.SaveChangesAsync();
    214             total = datatable.Rows.Count;
    215             if (autosave)
    216             {
    217               var filepath = Path.Combine(path, filename);
    218               file.OpenReadStream().Position = 0;
    219 
    220               using (var stream = System.IO.File.Create(filepath))
    221               {
    222                 await file.CopyToAsync(stream);
    223               }
    224             }
    225 
    226           }
    227         }
    228         watch.Stop();
    229         return Json(new { success = true, total = total, elapsedTime = watch.ElapsedMilliseconds });
    230       }
    231       catch (Exception e) {
    232         this._logger.LogError(e, "Excel導入失敗");
    233         return this.Json(new { success = false,  err = e.GetBaseException().Message });
    234       }
    235         }
    236     //下載模板
    237     public async Task<IActionResult> Download(string file) {
    238       
    239       this.Response.Cookies.Append("fileDownload", "true");
    240       var path = Path.Combine(this._webHostEnvironment.ContentRootPath, file);
    241       var downloadFile = new FileInfo(path);
    242       if (downloadFile.Exists)
    243       {
    244        var fileName = downloadFile.Name;
    245        var mimeType = MimeTypeConvert.FromExtension(downloadFile.Extension);
    246        var fileContent = new byte[Convert.ToInt32(downloadFile.Length)];
    247         using (var fs = downloadFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
    248         {
    249           await fs.ReadAsync(fileContent, 0, Convert.ToInt32(downloadFile.Length));
    250         }
    251         return this.File(fileContent, mimeType, fileName);
    252       }
    253       else
    254       {
    255         throw new FileNotFoundException($"文件 {file} 不存在!");
    256       }
    257     }
    258 
    259     }
    260 }

    View Code

    • 新建 View

    MVC Views\Companies\Index

      1 @model SmartAdmin.Data.Models.Company
      2 @{
      3   ViewData["Title"] = "企業信息";
      4   ViewData["PageName"] = "Companies_Index";
      5   ViewData["Heading"] = "<i class='fal fa-window text-primary'></i> 企業信息";
      6   ViewData["Category1"] = "組織架構";
      7   ViewData["PageDescription"] = "";
      8 }
      9 <div class="row">
     10   <div class="col-lg-12 col-xl-12">
     11     <div id="panel-1" class="panel">
     12       <div class="panel-hdr">
     13         <h2>
     14           公司信息
     15         </h2>
     16         <div class="panel-toolbar">
     17           <button class="btn btn-panel bg-transparent fs-xl w-auto h-auto rounded-0" data-action="panel-collapse" data-toggle="tooltip" data-offset="0,10" data-original-title="Collapse"><i class="fal fa-window-minimize"></i></button>
     18           <button class="btn btn-panel bg-transparent fs-xl w-auto h-auto rounded-0" data-action="panel-fullscreen" data-toggle="tooltip" data-offset="0,10" data-original-title="Fullscreen"><i class="fal fa-expand"></i></button>
     19         </div>
     20 
     21       </div>
     22       <div class="panel-container show">
     23         <div class="panel-content py-2 rounded-bottom border-faded border-left-0 border-right-0  text-muted bg-subtlelight-fade ">
     24           <div class="row no-gutters align-items-center">
     25             <div class="col">
     26               <!-- 開啟授權控制請參考 @@if (Html.IsAuthorize("Create") -->
     27               <div class="btn-group btn-group-sm">
     28                 <button onclick="append()" class="btn btn-default">
     29                   <span class="fal fa-plus mr-1"></span> 新增
     30                 </button>
     31               </div>
     32               <div class="btn-group btn-group-sm">
     33                 <button name="deletebutton" disabled onclick="removeit()" class="btn btn-default">
     34                   <span class="fal fa-times mr-1"></span> 刪除
     35                 </button>
     36               </div>
     37               <div class="btn-group btn-group-sm">
     38                 <button name="savebutton" disabled onclick="acceptChanges()" class="btn btn-default">
     39                   <span class="fal fa-save mr-1"></span> 保存
     40                 </button>
     41               </div>
     42               <div class="btn-group btn-group-sm">
     43                 <button name="cancelbutton" disabled onclick="rejectChanges()" class="btn btn-default">
     44                   <span class="fal fa-ban mr-1"></span> 取消
     45                 </button>
     46               </div>
     47               <div class="btn-group btn-group-sm">
     48                 <button onclick="reload()" class="btn btn-default"> <span class="fal fa-search mr-1"></span> 查詢 </button>
     49                 <button type="button" class="btn btn-default dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
     50                   <span class="sr-only">Toggle Dropdown</span>
     51                 </button>
     52                 <div class="dropdown-menu dropdown-menu-animated">
     53                   <a class="dropdown-item js-waves-on" href="javascript:void()"> 我的記錄 </a>
     54                   <div class="dropdown-divider"></div>
     55                   <a class="dropdown-item js-waves-on" href="javascript:void()"> 自定義查詢 </a>
     56                 </div>
     57               </div>
     58               <div class="btn-group btn-group-sm hidden-xs">
     59                 <button type="button" onclick="importExcel.upload()" class="btn btn-default"><span class="fal fa-cloud-upload mr-1"></span> 導入 </button>
     60                 <button type="button" class="btn btn-default  dropdown-toggle dropdown-toggle-split waves-effect waves-themed" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
     61                   <span class="sr-only">Toggle Dropdown</span>
     62                 </button>
     63                 <div class="dropdown-menu dropdown-menu-animated">
     64                   <a class="dropdown-item js-waves-on" href="javascript:importExcel.downloadtemplate()">
     65                     <span class="fal fa-download"></span> 下載模板
     66                   </a>
     67                 </div>
     68               </div>
     69               <div class="btn-group btn-group-sm hidden-xs">
     70                 <button onclick="exportexcel()" class="btn btn-default">
     71                   <span class="fal fa-file-export mr-1"></span>  導出
     72                 </button>
     73               </div>
     74 
     75             </div>
     76 
     77           </div>
     78 
     79         </div>
     80         <div class="panel-content">
     81           <div class="table-responsive">
     82             <table id="companies_datagrid">
     83             </table>
     84           </div>
     85         </div>
     86       </div>
     87     </div>
     88   </div>
     89 </div>
     90 <!-- 彈出窗體form表單 -->
     91 <div id="companydetailwindow" class="easyui-window"
     92      title="明細數據"
     93      data-options="modal:true,
     94                     closed:true,
     95                     minimizable:false,
     96                     collapsible:false,
     97                     maximized:false,
     98                     iconCls:'fal fa-window',
     99                     onBeforeClose:function(){
    100                       var that = $(this);
    101                       if(companyhasmodified()){
    102                         $.messager.confirm('確認','你確定要放棄保存修改的記錄?',function(r){
    103                         if (r){
    104                           var opts = that.panel('options');
    105                           var onBeforeClose = opts.onBeforeClose;
    106                           opts.onBeforeClose = function(){};
    107                           that.panel('close');
    108                           opts.onBeforeClose = onBeforeClose;
    109                           hook = false;
    110                         }
    111                         });
    112                         return false;
    113                       }
    114                     },
    115                     onOpen:function(){
    116                        $(this).window('vcenter');
    117                        $(this).window('hcenter');
    118                     },
    119                     onRestore:function(){
    120                     },
    121                     onMaximize:function(){
    122                     }
    123                     " style="width:820px;height:420px;display:none">
    124   <!-- toolbar -->
    125   <div class="panel-content py-2 rounded-bottom border-faded border-left-0 border-right-0  text-muted bg-subtlelight-fade sticky-top">
    126     <div class="d-flex flex-row-reverse pr-4">
    127       <div class="btn-group btn-group-sm mr-1">
    128         <button name="saveitembutton" onclick="savecompanyitem()" class="btn btn-default">
    129           <i class="fal fa-save"></i> 保存
    130         </button>
    131       </div>
    132       <div class="btn-group btn-group-sm mr-1" id="deleteitem-btn-group">
    133         <button onclick="deletecompanyitem()" class="btn btn-danger">
    134           <i class="fal fa-trash-alt"></i> 刪除
    135         </button>
    136       </div>
    137     </div>
    138   </div>
    139   <div class="panel-container show">
    140     <div class="container">
    141       <div class="panel-content">
    142         <form id="company_form"
    143               class="easyui-form form-horizontal p-1"
    144               method="post"
    145               data-options="novalidate:true,
    146                             onChange: function(target){
    147                               hook = true;
    148                               $('button[name*=\'saveitembutton\']').prop('disabled', false);
    149                              },
    150                              onLoadSuccess:function(data){
    151                                hook = false;
    152                                $('button[name*=\'saveitembutton\']').prop('disabled', true);
    153                              }">
    154           @Html.AntiForgeryToken()
    155           <!--Primary Key-->
    156           @Html.HiddenFor(model => model.Id)
    157           <fieldset class="form-group">
    158             <!-- begin row -->
    159             <!--名稱-->
    160             <div class="row h-100 justify-content-center align-items-center">
    161               <label class="col-md-2 pr-1 form-label text-right text-danger">@Html.DisplayNameFor(model => model.Name)</label>
    162               <div class="col-md-4 mb-1 pl-1">
    163                 <input id="@Html.IdFor(model => model.Name)"
    164                        name="@Html.NameFor(model => model.Name)"
    165                        value="@Html.ValueFor(model => model.Name)"
    166                        tabindex="0" required
    167                        class="easyui-textbox"
    168                        style="width:100%"
    169                        type="text"
    170                        data-options="prompt:'@Html.DescriptionFor(model => model.Name)',
    171                                  required:true,
    172                                  validType: 'length[0,50]'
    173                                  " />
    174               </div>
    175               <label class="col-md-2 pr-1 form-label text-right text-danger">@Html.DisplayNameFor(model => model.Code)</label>
    176               <div class="col-md-4 mb-1 pl-1">
    177                 <input id="@Html.IdFor(model => model.Code)"
    178                        name="@Html.NameFor(model => model.Code)"
    179                        value="@Html.ValueFor(model => model.Code)"
    180                        tabindex="1" required
    181                        class="easyui-textbox"
    182                        style="width:100%"
    183                        type="text"
    184                        data-options="prompt:'@Html.DescriptionFor(model => model.Code)',
    185                                  required:true,
    186                                  validType: 'length[0,12]'
    187                                  " />
    188               </div>
    189               <label class="col-md-2 pr-1 form-label text-right">@Html.DisplayNameFor(model => model.Address)</label>
    190               <div class="col-md-4 mb-1 pl-1">
    191                 <input id="@Html.IdFor(model => model.Address)"
    192                        name="@Html.NameFor(model => model.Address)"
    193                        value="@Html.ValueFor(model => model.Address)"
    194                        tabindex="2"
    195                        class="easyui-textbox"
    196                        style="width:100%"
    197                        type="text"
    198                        data-options="prompt:'@Html.DescriptionFor(model => model.Address)',
    199                                  required:false,
    200                                  validType: 'length[0,50]'
    201                                  " />
    202               </div>
    203               <label class="col-md-2 pr-1 form-label text-right">@Html.DisplayNameFor(model => model.Contect)</label>
    204               <div class="col-md-4 mb-1 pl-1">
    205                 <input id="@Html.IdFor(model => model.Contect)"
    206                        name="@Html.NameFor(model => model.Contect)"
    207                        value="@Html.ValueFor(model => model.Contect)"
    208                        tabindex="3"
    209                        class="easyui-textbox"
    210                        style="width:100%"
    211                        type="text"
    212                        data-options="prompt:'@Html.DescriptionFor(model => model.Contect)',
    213                                  required:false,
    214                                  validType: 'length[0,12]'
    215                                  " />
    216               </div>
    217               <label class="col-md-2 pr-1 form-label text-right">@Html.DisplayNameFor(model => model.PhoneNumber)</label>
    218               <div class="col-md-4 mb-1 pl-1">
    219                 <input id="@Html.IdFor(model => model.PhoneNumber)"
    220                        name="@Html.NameFor(model => model.PhoneNumber)"
    221                        value="@Html.ValueFor(model => model.PhoneNumber)"
    222                        tabindex="4"
    223                        class="easyui-textbox"
    224                        style="width:100%"
    225                        type="text"
    226                        data-options="prompt:'@Html.DescriptionFor(model => model.PhoneNumber)',
    227                                  required:false,
    228                                  validType: 'length[0,20]'
    229                                  " />
    230               </div>
    231               <label class="col-md-2 pr-1 form-label text-right text-danger">@Html.DisplayNameFor(model => model.RegisterDate)</label>
    232               <div class="col-md-4 mb-1 pl-1">
    233                 <input id="@Html.IdFor(model => model.RegisterDate)"
    234                        name="@Html.NameFor(model => model.RegisterDate)"
    235                        value="@Html.ValueFor(model => model.RegisterDate)"
    236                        tabindex="5" required
    237                        class="easyui-datebox"
    238                        style="width:100%"
    239                        type="text"
    240                        data-options="prompt:'@Html.DescriptionFor(model => model.RegisterDate)',
    241                                  required:true,
    242                                  formatter:dateformatter" />
    243               </div>
    244             </div>
    245           </fieldset>
    246         </form>
    247       </div>
    248     </div>
    249   </div>
    250 </div>
    251 
    252  
    253 @await Component.InvokeAsync("ImportExcel", new ImportExcelOptions { entity="Company",
    254   folder="Companies",
    255   url="/Companies/ImportExcel",
    256   tpl="/Companies/Download"
    257 
    258 
    259 })
    260 
    261 @section HeadBlock {
    262   <link href="~/css/notifications/toastr/toastr.css" rel="stylesheet" asp-append-version="true" />
    263   <link href="~/css/formplugins/bootstrap-daterangepicker/bootstrap-daterangepicker.css" rel="stylesheet" asp-append-version="true" />
    264   <link href="~/js/easyui/themes/insdep/easyui.css" rel="stylesheet" asp-append-version="true" />
    265 }
    266 @section ScriptsBlock {
    267   <script src="~/js/dependency/moment/moment.js" asp-append-version="true"></script>
    268   <script src="~/js/notifications/toastr/toastr.js"></script>
    269   <script src="~/js/formplugins/bootstrap-daterangepicker/bootstrap-daterangepicker.js" asp-append-version="true"></script>
    270   <script src="~/js/easyui/jquery.easyui.min.js" asp-append-version="true"></script>
    271   <script src="~/js/easyui/plugins/datagrid-filter.js" asp-append-version="true"></script>
    272   <script src="~/js/easyui/plugins/columns-ext.js" asp-append-version="true"></script>
    273   <script src="~/js/easyui/plugins/columns-reset.js" asp-append-version="true"></script>
    274   <script src="~/js/easyui/locale/easyui-lang-zh_CN.js" asp-append-version="true"></script>
    275   <script src="~/js/easyui/jquery.easyui.component.js" asp-append-version="true"></script>
    276   <script src="~/js/plugin/filesaver/FileSaver.js" asp-append-version="true"></script>
    277   <script src="~/js/plugin/jquery.serializejson/jquery.serializejson.js" asp-append-version="true"></script>
    278   <script src="~/js/jquery.custom.extend.js" asp-append-version="true"></script>
    279   <script src="~/js/jquery.extend.formatter.js" asp-append-version="true"></script>
    280   <script>
    281         var $dg = $('#companies_datagrid');
    282         var EDITINLINE = true;
    283         var company = null;
    284     var editIndex = undefined;
    285     //下載Excel導入模板
    286 
    287     //執行導出下載Excel
    288     function exportexcel() {
    289       const filterRules = JSON.stringify($dg.datagrid('options').filterRules);
    290       console.log(filterRules);
    291       $.messager.progress({ title: '請等待',msg:'正在執行導出...' });
    292       let formData = new FormData();
    293       formData.append('filterRules', filterRules);
    294       formData.append('sort', 'Id');
    295       formData.append('order', 'asc');
    296       $.postDownload('/Companies/ExportExcel', formData).then(res => {
    297         $.messager.progress('close');
    298         toastr.success('導出成功!');
    299       }).catch(err => {
    300         //console.log(err);
    301         $.messager.progress('close');
    302         $.messager.alert('導出失敗', err.statusText, 'error');
    303       });
    304 
    305     }
    306             //彈出明細信息
    307     function showdetailswindow(id, index) {
    308       const company = $dg.datagrid('getRows')[index];
    309       opencompanydetailwindow(company, 'Modified');
    310     }
    311         function reload() {
    312                $dg.datagrid('uncheckAll');
    313                $dg.datagrid('reload');
    314         }
    315             //新增記錄
    316        function append() {
    317                 company = {
    318                     Address: '-',
    319                     RegisterDate: moment().format('YYYY-MM-DD HH:mm:ss'),
    320                 };
    321                 if (!EDITINLINE) {
    322                     //彈出新增窗口
    323                     opencompanydetailwindow(company, 'Added');
    324                 } else {
    325                     if (endEditing()) {
    326                         //對必填字段進行默認值初始化
    327                        $dg.datagrid('insertRow',
    328                             {
    329                                 index: 0,
    330                                 row: company
    331                             });
    332                         editIndex = 0;
    333                        $dg.datagrid('selectRow', editIndex)
    334                             .datagrid('beginEdit', editIndex);
    335                         hook = true;
    336                     }
    337                 }
    338         }
    339             //刪除編輯的行
    340         function removeit() {
    341                 if (this.$dg.datagrid('getChecked').length <= 0 && EDITINLINE) {
    342                     if (editIndex !== undefined) {
    343                         const delindex = editIndex;
    344                        $dg.datagrid('cancelEdit', delindex)
    345                             .datagrid('deleteRow', delindex);
    346                         hook = true;
    347                     } else {
    348                         const rows =$dg.datagrid('getChecked');
    349                         rows.slice().reverse().forEach(row => {
    350                             const rowindex =$dg.datagrid('getRowIndex', row);
    351                            $dg.datagrid('deleteRow', rowindex);
    352                             hook = true;
    353                         });
    354                     }
    355                 } else {
    356                     deletechecked();
    357                 }
    358         }
    359             //刪除該行
    360         function deleteRow(id) {
    361             $.messager.confirm('確認', '你確定要刪除該記錄?', result => {
    362                 if (result) {
    363                     dodeletechecked([id]);
    364                 }
    365             })
    366         }
    367             //刪除選中的行
    368          function deletechecked() {
    369                 const id =$dg.datagrid('getChecked').filter(item => item.Id != null && item.Id > 0).map(item => {
    370                     return item.Id;
    371                 });
    372                 if (id.length > 0) {
    373                     $.messager.confirm('確認', `你確定要刪除這 <span class='badge badge-icon position-relative'>${id.length} </span> 行記錄?`, result => {
    374                         if (result) {
    375                             dodeletechecked(id);
    376                         }
    377                     });
    378                 } else {
    379                     $.messager.alert('提示', '請先選擇要刪除的記錄!', 'question');
    380                 }
    381         }
    382             //執行刪除
    383         function dodeletechecked(id) {
    384             $.post('/Companies/DeleteChecked', { id: id })
    385                 .done(response => {
    386                     if (response.success) {
    387                         toastr.error(`成功刪除[${id.length}]行記錄`);
    388                         reload();
    389                     } else {
    390                         $.messager.alert('錯誤', response.err, 'error');
    391                     }
    392                 })
    393                 .fail((jqXHR, textStatus, errorThrown) => {
    394                     $.messager.alert('異常', `${jqXHR.status}: ${jqXHR.statusText} `, 'error');
    395                 });
    396         }
    397             //開啟編輯狀態
    398     function onClickCell(index, field) {
    399 
    400       company = $dg.datagrid('getRows')[index];
    401       const _actions = ['action', 'ck'];
    402       if (!EDITINLINE || $.inArray(field, _actions) >= 0) {
    403         return;
    404       }
    405 
    406       if (editIndex !== index) {
    407         if (endEditing()) {
    408           $dg.datagrid('selectRow', index)
    409             .datagrid('beginEdit', index);
    410           hook = true;
    411           editIndex = index;
    412           const ed = $dg.datagrid('getEditor', { index: index, field: field });
    413           if (ed) {
    414             ($(ed.target).data('textbox') ? $(ed.target).textbox('textbox') : $(ed.target)).focus();
    415           }
    416         } else {
    417           $dg.datagrid('selectRow', editIndex);
    418         }
    419       }
    420     }
    421             //關閉編輯狀態
    422     function endEditing() {
    423 
    424             if (editIndex === undefined) {
    425                 return true;
    426             }
    427             if (this.$dg.datagrid('validateRow', editIndex)) {
    428                 $dg.datagrid('endEdit', editIndex);
    429                 return true;
    430             } else {
    431                 const invalidinput = $('input.validatebox-invalid', $dg.datagrid('getPanel'));
    432                 const fieldnames = invalidinput.map((index, item) => {
    433                     return $(item).attr('placeholder') || $(item).attr('id');
    434                 });
    435                 $.messager.alert('提示', `${Array.from(fieldnames)} 輸入有誤.`, 'error');
    436                 return false;
    437             }
    438         }
    439             //提交保存後台數據庫
    440         function acceptChanges() {
    441             if (endEditing()) {
    442                 if ($dg.datagrid('getChanges').length > 0) {
    443                     const inserted = $dg.datagrid('getChanges', 'inserted').map(item => {
    444                         item.TrackingState = 1;
    445                         return item;
    446                     });
    447                     const updated = $dg.datagrid('getChanges', 'updated').map(item => {
    448                         item.TrackingState = 2
    449                         return item;
    450                     });
    451                     const deleted = $dg.datagrid('getChanges', 'deleted').map(item => {
    452                         item.TrackingState = 3
    453                         return item;
    454                     });
    455                     //過濾已刪除的重複項
    456                     const changed = inserted.concat(updated.filter(item => {
    457                         return !deleted.includes(item);
    458                     })).concat(deleted);
    459                     //$.messager.progress({ title: '請等待', msg: '正在保存數據...', interval: 200 });
    460                     $.post('/Companies/AcceptChanges', { companies: changed })
    461                         .done(response => {
    462                             //$.messager.progress('close');
    463                             //console.log(response);
    464                             if (response.success) {
    465                                 toastr.success('保存成功');
    466                                 $dg.datagrid('acceptChanges');
    467                                 reload();
    468                                 hook = false;
    469                             } else {
    470                                 $.messager.alert('錯誤', response.err, 'error');
    471                             }
    472                         })
    473                         .fail((jqXHR, textStatus, errorThrown) => {
    474                             //$.messager.progress('close');
    475                             $.messager.alert('異常', `${jqXHR.status}: ${jqXHR.statusText} `, 'error');
    476                         });
    477                 }
    478             }
    479         }
    480         function rejectChanges() {
    481             $dg.datagrid('rejectChanges');
    482             editIndex = undefined;
    483             hook = false;
    484         }
    485     $(document).ready(function () {
    486       //定義datagrid結構
    487       $dg.datagrid({
    488         rownumbers: true,
    489         checkOnSelect: false,
    490         selectOnCheck: false,
    491         idField: 'Id',
    492         sortName: 'Id',
    493         sortOrder: 'desc',
    494         remoteFilter: true,
    495         singleSelect: true,
    496         method: 'get',
    497         onClickCell: onClickCell,
    498         clientPaging: false,
    499         pagination: true,
    500         striped: true,
    501         filterRules: [],
    502         onHeaderContextMenu: function (e, field) {
    503           e.preventDefault();
    504           $(this).datagrid('columnMenu').menu('show', {
    505             left: e.pageX,
    506             top: e.pageY
    507           });
    508         },
    509         onBeforeLoad: function () {
    510           const that = $(this);
    511           document.addEventListener('panel.onfullscreen', () => {
    512             setTimeout(() => {
    513               that.datagrid('resize');
    514             }, 200)
    515           })
    516         },
    517         onLoadSuccess: function (data) {
    518           editIndex = undefined;
    519           $("button[name*='deletebutton']").prop('disabled', true);
    520           $("button[name*='savebutton']").prop('disabled', true);
    521           $("button[name*='cancelbutton']").prop('disabled', true);
    522         },
    523         onCheck: function () {
    524           $("button[name*='deletebutton']").prop('disabled', false);
    525         },
    526         onUncheck: function () {
    527           const checked = $(this).datagrid('getChecked').length > 0;
    528           $("button[name*='deletebutton']").prop('disabled', !checked);
    529         },
    530         onSelect: function (index, row) {
    531           company = row;
    532         },
    533         onBeginEdit: function (index, row) {
    534           //const editors = $(this).datagrid('getEditors', index);
    535 
    536         },
    537         onEndEdit: function (index, row) {
    538           editIndex = undefined;
    539         },
    540         onBeforeEdit: function (index, row) {
    541           editIndex = index;
    542           row.editing = true;
    543           $("button[name*='deletebutton']").prop('disabled', false);
    544           $("button[name*='cancelbutton']").prop('disabled', false);
    545           $("button[name*='savebutton']").prop('disabled', false);
    546           $(this).datagrid('refreshRow', index);
    547         },
    548         onAfterEdit: function (index, row) {
    549           row.editing = false;
    550           editIndex = undefined;
    551           $(this).datagrid('refreshRow', index);
    552         },
    553         onCancelEdit: function (index, row) {
    554           row.editing = false;
    555           editIndex = undefined;
    556           $("button[name*='deletebutton']").prop('disabled', true);
    557           $("button[name*='savebutton']").prop('disabled', true);
    558           $("button[name*='cancelbutton']").prop('disabled', true);
    559           $(this).datagrid('refreshRow', index);
    560         },
    561         frozenColumns: [[
    562           /*開啟CheckBox選擇功能*/
    563           { field: 'ck', checkbox: true },
    564           {
    565             field: 'action',
    566             title: '操作',
    567             width: 85,
    568             sortable: false,
    569             resizable: true,
    570             formatter: function showdetailsformatter(value, row, index) {
    571               if (!row.editing) {
    572                 return `<div class="btn-group">\
    573                                                          <button onclick="showdetailswindow('${row.Id}',  ${index})" class="btn btn-primary btn-sm btn-icon waves-effect waves-themed" title="查看明細" ><i class="fal fa-edit"></i> </button>\
    574                                                          <button onclick="deleteRow('${row.Id}',${index})" class="btn btn-primary btn-sm btn-icon waves-effect waves-themed" title="刪除記錄" ><i class="fal fa-times"></i> </button>\
    575                                                     </div>`;
    576               } else {
    577                 return `<button class="btn btn-primary btn-sm btn-icon waves-effect waves-themed" disabled title="查看明細"  ><i class="fal fa-edit"></i> </button>`;
    578               }
    579             }
    580           }
    581         ]],
    582         columns: [[
    583 
    584           {    /*名稱*/
    585             field: 'Name',
    586             title: '<span class="required">@Html.DisplayNameFor(model => model.Name)</span>',
    587             width: 200,
    588             hidden: false,
    589             editor: {
    590               type: 'textbox',
    591               options: { prompt: '@Html.DescriptionFor(model => model.Name)', required: true, validType: 'length[0,50]' }
    592             },
    593             sortable: true,
    594             resizable: true
    595           },
    596           {    /*組織代碼*/
    597             field: 'Code',
    598             title: '<span class="required">@Html.DisplayNameFor(model => model.Code)</span>',
    599             width: 120,
    600             hidden: false,
    601             editor: {
    602               type: 'textbox',
    603               options: { prompt: '@Html.DescriptionFor(model => model.Code)', required: true, validType: 'length[0,12]' }
    604             },
    605             sortable: true,
    606             resizable: true
    607           },
    608           {    /*地址*/
    609             field: 'Address',
    610             title: '@Html.DisplayNameFor(model => model.Address)',
    611             width: 200,
    612             hidden: false,
    613             editor: {
    614               type: 'textbox',
    615               options: { prompt: '@Html.DescriptionFor(model => model.Address)', required: false, validType: 'length[0,50]' }
    616             },
    617             sortable: true,
    618             resizable: true
    619           },
    620           {    /*聯繫人*/
    621             field: 'Contect',
    622             title: '@Html.DisplayNameFor(model => model.Contect)',
    623             width: 120,
    624             hidden: false,
    625             editor: {
    626               type: 'textbox',
    627               options: { prompt: '@Html.DescriptionFor(model => model.Contect)', required: false, validType: 'length[0,12]' }
    628             },
    629             sortable: true,
    630             resizable: true
    631           },
    632           {    /*聯繫電話*/
    633             field: 'PhoneNumber',
    634             title: '@Html.DisplayNameFor(model => model.PhoneNumber)',
    635             width: 120,
    636             hidden: false,
    637             editor: {
    638               type: 'textbox',
    639               options: { prompt: '@Html.DescriptionFor(model => model.PhoneNumber)', required: false, validType: 'length[0,20]' }
    640             },
    641             sortable: true,
    642             resizable: true
    643           },
    644           {   /*註冊日期*/
    645             field: 'RegisterDate',
    646             title: '<span class="required">@Html.DisplayNameFor(model => model.RegisterDate)</span>',
    647             width: 140,
    648             align: 'right',
    649             hidden: false,
    650             editor: {
    651               type: 'datebox',
    652               options: { prompt: '@Html.DescriptionFor(model => model.RegisterDate)', required: true }
    653             },
    654             formatter: dateformatter,
    655             sortable: true,
    656             resizable: true
    657           },
    658         ]]
    659       }).datagrid('columnMoving')
    660         .datagrid('resetColumns')
    661         .datagrid('enableFilter', [
    662           {   /*Id*/
    663             field: 'Id',
    664             type: 'numberbox',
    665             op: ['equal', 'notequal', 'less', 'lessorequal', 'greater', 'greaterorequal']
    666           },
    667           {     /*註冊日期*/
    668             field: 'RegisterDate',
    669             type: 'dateRange',
    670             options: {
    671               onChange: value => {
    672                 $dg.datagrid('addFilterRule', {
    673                   field: 'RegisterDate',
    674                   op: 'between',
    675                   value: value
    676                 });
    677 
    678                 $dg.datagrid('doFilter');
    679               }
    680             }
    681           },
    682         ])
    683         .datagrid('load', '/Companies/GetData');
    684     }
    685     );
    686 
    687   </script>
    688   <script type="text/javascript">
    689     //判斷新增編輯狀態
    690     var MODELSTATE = 'Added';
    691     var companyid = null;
    692     function opencompanydetailwindow(data, state) {
    693       MODELSTATE = state;
    694       initcompanydetailview();
    695       companyid = (data.Id || 0);
    696       $("#companydetailwindow").window("open");
    697       $('#company_form').form('reset');
    698       $('#company_form').form('load', data);
    699     }
    700     //刪除當前記錄
    701     function deletecompanyitem() {
    702       $.messager.confirm('確認', '你確定要刪除該記錄?', result => {
    703         if (result) {
    704           const url = `/Companies/Delete/${companyid}`;
    705           $.get(url).done(res => {
    706             if (res.success) {
    707               toastr.success("刪除成功");
    708               $("#companydetailwindow").window("close");
    709               reload();
    710             } else {
    711               $.messager.alert("錯誤", res.err, "error");
    712             }
    713           });
    714         }
    715       });
    716     }
    717     //async 保存數據
    718     async function savecompanyitem() {
    719       const $companyform = $('#company_form');
    720       if ($companyform.form('enableValidation').form('validate')) {
    721         let company = $companyform.serializeJSON();
    722         let url = '/Companies/Edit';
    723         //判斷是新增或是修改方法
    724         if (MODELSTATE === 'Added') {
    725           url = '/Companies/Create';
    726         }
    727         var token = $('input[name="__RequestVerificationToken"]', $companyform).val();
    728         //$.messager.progress({ title: '請等待', msg: '正在保存數據...', interval: 200 });
    729         $.ajax({
    730           type: "POST",
    731           url: url,
    732           data: {
    733             __RequestVerificationToken: token,
    734             company: company
    735           },
    736           dataType: 'json',
    737           contentType: 'application/x-www-form-urlencoded; charset=utf-8'
    738         })
    739           .done(response => {
    740             //$.messager.progress('close');
    741             if (response.success) {
    742               hook = false;
    743               $companyform.form('disableValidation');
    744               $dg.datagrid('reload');
    745               $('#companydetailwindow').window("close");
    746               toastr.success("保存成功");
    747             } else {
    748               $.messager.alert("錯誤", response.err, "error");
    749             }
    750           })
    751           .fail((jqXHR, textStatus, errorThrown) => {
    752             //$.messager.progress('close');
    753             $.messager.alert('異常', `${jqXHR.status}: ${jqXHR.statusText} `, 'error');
    754           });
    755       }
    756     }
    757     //關閉窗口
    758     function closecompanydetailwindow() {
    759       $('#companydetailwindow').window('close');
    760     }
    761 
    762     //判斷是否有沒有保存的記錄
    763     function companyhasmodified() {
    764       return hook;
    765     }
    766 
    767 
    768     function initcompanydetailview() {
    769       //判斷是否显示功能按鈕
    770       if (MODELSTATE === 'Added') {
    771         $('#deleteitem-btn-group').hide();
    772       } else {
    773         $('#deleteitem-btn-group').show();
    774       }
    775 
    776       //回車光標移動到下個輸入控件
    777       //日期類型 註冊日期
    778       $('#RegisterDate').datebox('textbox').bind('keydown', function (e) {
    779         if (e.keyCode == 13) {
    780           $(e.target).emulateTab();
    781         }
    782       });
    783     }
    784   </script>
    785 }

    View Code

     

    上面View層的代碼非常的複雜,但都是固定格式,可以用scaffold快速生成

    • 配置依賴注入(DI),註冊服務

    打開 startup.cs 在 public void ConfigureServices(IServiceCollection services) 註冊服務 services.AddScoped<IRepositoryX, RepositoryX>(); 
    services.AddScoped<ICustomerService, CustomerService>();

    • 更新數據庫

    EF Core Code-First 同步更新數據庫 
    在 Visual Studio.Net 
    Package Manager Controle 運行 
    PM>:add-migration create_Company 
    PM>:update-database 
    PM>:更新完成

    • Debug 運行項目 

    高級應用

    CAP 分佈式事務的解決方案及應用場景 
    nuget 安裝組件 
    PM> Install-Package DotNetCore.CAP 
    PM> Install-Package DotNetCore.CAP.RabbitMQ 
    PM> Install-Package DotNetCore.CAP.SqlServer \

    • 配置Startup.cs
     1 public void ConfigureServices(IServiceCollection services)
     2     {
     3       services.AddCap(x =>
     4       {
     5         x.UseEntityFramework<SmartDbContext>();
     6         x.UseRabbitMQ("127.0.0.1");
     7         x.UseDashboard();
     8         x.FailedRetryCount = 5;
     9         x.FailedThresholdCallback = failed =>
    10         {
    11           var logger = failed.ServiceProvider.GetService<ILogger<Startup>>();
    12           logger.LogError($@"A message of type {failed.MessageType} failed after executing {x.FailedRetryCount} several times, 
    13                         requiring manual troubleshooting. Message name: {failed.Message.GetName()}");
    14         };
    15       });
    16     }

    View Code

     

    • 發布消息
    • 訂閱消息

    roadmap

    • 完善主要的開發文檔
    • 支持My SQL數據庫
    • 還會繼續重構和完善代碼
    • 開發Scaffold MVC模板,生成定製化的Controller 和 View 減少開發人員重複工作
    • 完善授權訪問策略(policy-based authorization)
    • 開發Visual Sutdio.net代碼生成插件(類似國內做比較好的52abp)

    我的聯繫方式,qq群,贊助二維碼

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • 京都龜岡市將禁止提供塑膠袋 業者反彈起步困難

    摘錄自2020年3月17日Yahoo!新聞報導

    日本京都的龜岡市領先全國通過「塑膠袋禁止條例」,今後在當地買東西結帳,就算願意付錢也不會提供塑膠袋,擅自提供的店家還會被罰款。這項規定原本預定8月上路,但因為市民團體反應還需要適應的時間,因此目前還沒有訂下確切的實行時間。

    要改變基本生活習慣並不容易。就怕居民反對,龜岡市從去年到今年前後辦了50場說明會。但有民眾認為,要是沒和鄰近的市鎮合作,很難辦到完全禁用。根據說明會後的統計,贊成這項規定的市民高達7成5。事實上從塑膠袋要收費開始,就能看到明顯改變,帶購物袋來採買的民眾從原本的20%,大幅增加為80%。

    這一頭成功安撫民眾,另一頭還得得到超市等業者的支持。有超市業者表示,就算現在訂購紙袋,到進貨為止也要2個月。日本加盟協會負責人也表示:「像加熱的關東煮、焗烤、杯麵,得加熱湯讓客人帶走就無法賣了,毫無疑問會對業績造成影響,這可是生死存亡的問題,說真心話希望能多點緩衝時間。」

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 熱浪席捲 澳洲全國均溫40.9度創新高

    摘錄自2019年12月18日中央社報導

    澳17日出現有紀錄以來最熱的一天,氣象局測到全國平均氣溫高達攝氏40.9度(華氏105.6度)。先前澳洲高溫紀錄為2013年1月時創下的攝氏40.3度。

    在近日野火肆虐澳洲東岸,又有熱浪席捲各地,氣候狀況更形惡劣的情況下,高溫紀錄預料很快就會再寫新高。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※教你寫出一流的銷售文案?

  • 新柴油車微粒排放超標 歐洲環保團體籲從嚴限制

    摘錄自2020年01月13日中央通訊社報導

    總部在比利時的歐洲運輸環境聯合會今天(13日)表示,有些新款柴油車在清理濾清器時微粒排放量嚴重超標,呼籲歐洲議會議員制定更嚴格的排放檢測標準與規範。

    德國福斯汽車(Volkswagen)在2018年歐洲地區銷售最佳的兩款柴油車執行檢測時發現,在定期進行清理車內防污濾清器的程序時,微粒污染程度恐飆到正常標準1000倍。

    路透社報導,針對日產汽車(Nissan)Qashqai 與歐寶汽車(Opel)/ 沃豪汽車(Vauxhal)Astra兩款柴油車執行的檢測發現,自動清理濾清器時,微粒的排放量高於標準32%至115%。歐寶 / 沃豪的發言人表示,因不了解運輸環境聯合會所公布報告的細節,無法置評。

    運輸環境聯合會表示,歐洲超過4500萬輛汽車安裝微粒濾清器,每年得清理濾清器13億次。清理的程序可能兩週就有一次,並可持續15公里距離。聯合會指出,根據歐盟現行法規,若是車子在官方檢測期間清理濾清器,檢測結果不列入統計,「也就是說,受檢測車輛所排放的微粒有60%至99%受到忽視」。

    根據世界衛生組織(WHO),懸浮微粒對人們的影響比其他污染物更嚴重,另據歐洲環保署(European Environment Agency)指出,歐洲各大城市裡,有3/4居民暴露於不安全的懸浮微粒等級。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※推薦評價好的iphone維修中心

  • 捨電動車 韓國現代氫動力貨卡將上市 目標零碳排征服瑞士高山

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 防堵武漢肺炎 美國家公園仍營業 遊客湧入憂成防疫破口

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • 研究:為躲避赤道高溫 海洋生物逐漸往極地方向移動

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※教你寫出一流的銷售文案?

  • 緬甸遭洪水侵襲 11人死12萬人疏散

    摘錄自2018年7月31日大紀元紐約報導

    受雨季帶來的豪雨影響,緬甸中部和南部多個村莊遭洪水侵襲,導致11人死亡,另有大約12萬人被迫撤離家園。

    緬甸官員在星期二(7月31日)告訴法新社說,迄今已有超過11萬8000人被安置在285個避難營地,而死亡人數已上升至11人,其中包括3名士兵。死亡人數可能還會增加。

    除了因洪水而喪生的罹難者之外,在緬甸南部的高當鎮(Kawthaung Township),另有5人因豪雨造成的泥石流而失去生命。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 福島核污染物遭沖走外洩 韓國要求日本提供資料

    摘錄自2019年10月21日自由時報報導

    哈吉貝颱風侵襲日本,造成許多地區出現慘重水患,放置福島核電事故輻射污染物的臨時貯存場也遭大水淹沒,部分裝有輻射污染廢棄物的袋子沖入河中,輻射廢棄物不翼而飛,韓國核電安全委員會要求日本提供該事件相關資料。

    據《韓聯社》報導,韓核電安全委員會委員長嚴在植21日透露,針對福島輻射污染物垃圾袋被沖走一事,已要求日本駐韓大使館提供相關資料,日前韓奧委會也在與國際奧會(IOC)主席巴赫(Thomas Bach)會談時,向巴赫提出福島輻射污染物洩漏事件,巴赫表示會計畫確認情況。

    日本環境省表示,目前在河道的輻射量並沒有變化,認為這些流出的放射性物質濃度較低,對環境不會有影響。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※教你寫出一流的銷售文案?

  • 瑞士國會選舉初步預測 綠黨可望竄升成第4大黨

    摘錄自2019年10月21日中央社報導

    士國會大選投票20日落幕,初步預測顯示,瑞士人民黨可望拿下25.8%得票率蟬聯第一大黨。

    瑞士廣播電視台(SRF)公布民調機構GFS Bern的預測結果,顯示綠黨(Green Party)有望在下議院選舉獲得13%選票,並從上屆選舉第5大黨竄升成為第4大黨。另一小黨綠色自由黨(Green Liberal Party,GLP)得票率則有7.9%。

    路透社報導,如果綠黨與綠色自由黨克服政策分歧並決定團結合作,兩黨的得票率將逼近21%,甚至有望在最高行政機關聯邦委員會(Federal Council)7名委員中奪下一席。

    根據政治研究機構索托莫研究所(Sotomo),氣候變遷取代移民政策,成為瑞士選民最關心的議題。這是瑞士近代政治史上,最顯著的轉變之一。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※推薦評價好的iphone維修中心