TemplateData;
+
+ ///
+ /// 要导入的Model列表
+ ///
+ [JsonIgnore]
+ public List EntityList { get; set; }
+
+ ///
+ /// 模版
+ ///
+ [JsonIgnore]
+ public T Template { get; set; }
+
+ ///
+ /// Model数据是否已被赋值
+ ///
+ protected bool isEntityListSet = false;
+
+ ///
+ /// 声明XSSF
+ ///
+ protected XSSFWorkbook xssfworkbook;
+
+ ///
+ /// 唯一性验证
+ ///
+ protected DuplicatedInfo
finalInfo;
+
+ ///
+ /// 是否存在主子表
+ ///
+ protected bool HasSubTable { get; set; }
+
+ ///
+ /// 是否在sqlserver时使用bulk导入
+ ///
+ public bool UseBulkSave { get; set; }
+
+ ///
+ /// 是否覆盖已有数据
+ ///
+ public bool IsOverWriteExistData { get; set; } = true;
+ #endregion
+
+ #region 构造函数
+ public BaseImportVM()
+ {
+ ErrorListVM = new TemplateErrorListVM();
+ ValidityTemplateType = true;
+ Template = new T();
+ }
+ #endregion
+
+ #region 生成excel
+ ///
+ /// 生成模版
+ ///
+ /// 模版文件名
+ /// 生成的模版
+ public virtual byte[] GenerateTemplate(out string displayName)
+ {
+ return Template.GenerateTemplate(out displayName);
+ }
+ #endregion
+
+ #region 设置参数值
+ ///
+ /// 设置模版参数
+ ///
+ /// 参数
+ public void SetParms(Dictionary parms)
+ {
+ Template.Parms = parms;
+ }
+ #endregion
+
+ #region 可重写方法
+
+ ///
+ /// 设置数据唯一性验证,子类中如果需要数据唯一性验证,应重写此方法
+ ///
+ /// 唯一性属性
+ public virtual DuplicatedInfo SetDuplicatedCheck()
+ {
+ return null;
+ }
+
+ ///
+ /// 获取上传的结果值
+ ///
+ public virtual void SetEntityList()
+ {
+ if (!isEntityListSet)
+ {
+ EntityList = new List
();
+
+ //初始化上传的模板数据
+ SetTemplateData();
+
+ //如果模板中有错误,直接返回
+ if (ErrorListVM.EntityList.Count > 0)
+ {
+ return;
+ }
+
+ //对EntityList赋值
+ SetEntityData();
+
+ //设置标识为初始化
+ isEntityListSet = true;
+ }
+ }
+
+ ///
+ /// 获取上传模板中填写的数据,包含了对模板正确性的验证
+ ///
+ public virtual void SetTemplateData()
+ {
+ if (TemplateData != null && TemplateData.Count > 0)
+ {
+ return;
+ }
+
+ try
+ {
+ TemplateData = new List();
+ xssfworkbook = new XSSFWorkbook();
+
+ //【CHECK】上传附件的ID为空
+ if (UploadFileId == null)
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = CoreProgram._localizer?["Sys.PleaseUploadTemplate"] });
+ return;
+ }
+ Models.IWtmFile file = null;
+ if (Wtm.ServiceProvider != null)
+ {
+ var fp = Wtm.ServiceProvider.GetRequiredService();
+ file = fp.GetFile(UploadFileId, true, DC);
+ }
+ if (file == null)
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = CoreProgram._localizer?["Sys.WrongTemplate"] });
+ return;
+ }
+ xssfworkbook = new XSSFWorkbook(file.DataStream);
+ file.DataStream.Dispose();
+ Template.InitExcelData();
+ Template.InitCustomFormat();
+
+ //【CHECK】判断是否上传的是正确的模板数据
+ string TemplateHiddenName = xssfworkbook.GetSheetAt(1).GetRow(0).Cells[2].ToString();
+ if (ValidityTemplateType && !TemplateHiddenName.Equals(typeof(T).Name))
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = CoreProgram._localizer?["Sys.WrongTemplate"] });
+ return;
+ }
+
+ //获取数据的Sheet页信息
+ ISheet sheet = xssfworkbook.GetSheetAt(0);
+ sheet.ForceFormulaRecalculation = true;
+ XSSFFormulaEvaluator XE = new XSSFFormulaEvaluator(xssfworkbook);
+ IEnumerator rows = sheet.GetRowEnumerator();
+ var cells = sheet.GetRow(0).Cells;
+
+ //获取模板中所有字段的属性
+ List ListTemplateProptetys = new List();
+ var ListPropetys = Template.GetType().GetFields().Where(x => x.FieldType == typeof(ExcelPropety)).ToList();
+ for (int i = 0; i < ListPropetys.Count(); i++)
+ {
+ ExcelPropety ep = (ExcelPropety)ListPropetys[i].GetValue(Template);
+ ListTemplateProptetys.Add(ep);
+ }
+
+ //【CHECK】验证模板的列数是否正确
+ var dynamicColumn = ListTemplateProptetys.Where(x => x.DataType == ColumnDataType.Dynamic).FirstOrDefault();
+ int columnCount = dynamicColumn == null ? ListTemplateProptetys.Count : (ListTemplateProptetys.Count + dynamicColumn.DynamicColumns.Count - 1);
+ if (columnCount != cells.Count)
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = CoreProgram._localizer?["Sys.WrongTemplate"] });
+ return;
+ }
+
+ //【CHECK】判断字段是否根据顺序能一对一相对应。 //是否可以去除?
+ int pIndex = 0;
+ HasSubTable = false;
+ for (int i = 0; i < cells.Count; i++)
+ {
+ //是否有子表
+ HasSubTable = ListTemplateProptetys[pIndex].SubTableType != null ? true : HasSubTable;
+ //if (ListTemplateProptetys[pIndex].DataType != ColumnDataType.Dynamic)
+ //{
+ // if (cells[i].ToString().Trim('*') != ListTemplateProptetys[pIndex].ColumnName)
+ // {
+ // ErrorListVM.EntityList.Add(new ErrorMessage { Message = CoreProgram._localizer?["Sys.WrongTemplate"] });
+ // return;
+ // }
+ // pIndex++;
+ //}
+ //else
+ //{
+ // var listDynamicColumns = ListTemplateProptetys[i].DynamicColumns;
+ // int dcCount = listDynamicColumns.Count;
+ // for (int dclIndex = 0; dclIndex < dcCount; dclIndex++)
+ // {
+ // if (cells[i].ToString().Trim('*') != listDynamicColumns[dclIndex].ColumnName)
+ // {
+ // ErrorListVM.EntityList.Add(new ErrorMessage { Message = CoreProgram._localizer?["Sys.WrongTemplate"] });
+ // break;
+ // }
+ // i = i + 1;
+ // }
+ // i = i - 1;
+ pIndex++;
+ //}
+ }
+
+ //如果有子表,则设置主表字段非必填
+ if (HasSubTable)
+ {
+ for (int i = 0; i < cells.Count; i++)
+ {
+ ListTemplateProptetys[i].IsNullAble = ListTemplateProptetys[i].SubTableType == null ? true : ListTemplateProptetys[i].IsNullAble;
+ }
+ }
+
+ //向TemplateData中赋值
+ int rowIndex = 2;
+ rows.MoveNext();
+ while (rows.MoveNext())
+ {
+ XSSFRow row = (XSSFRow)rows.Current;
+ if (IsEmptyRow(row, columnCount))
+ {
+ return;
+ }
+
+ T result = new T();
+ pIndex = 0;
+ for (int i = 0; i < columnCount; i++)
+ {
+ //获取列的值
+ string value = row.GetCell(i, MissingCellPolicy.CREATE_NULL_AS_BLANK).ToString();
+ ExcelPropety excelPropety = CopyExcelPropety(ListTemplateProptetys[pIndex]);
+
+ if (excelPropety.DataType == ColumnDataType.Text)
+ {
+ ICell cell = row.GetCell(i);
+ value = GetCellFormulaValue(XE, cell, value);
+ }
+
+ if (excelPropety.DataType == ColumnDataType.Dynamic)
+ {
+ int dynamicColCount = excelPropety.DynamicColumns.Count();
+ for (int dynamicColIndex = 0; dynamicColIndex < dynamicColCount; dynamicColIndex++)
+ {
+ excelPropety.DynamicColumns[dynamicColIndex].ValueValidity(row.GetCell(i + dynamicColIndex, MissingCellPolicy.CREATE_NULL_AS_BLANK).ToString(), ErrorListVM.EntityList, rowIndex);
+ }
+ i = i + dynamicColCount - 1;
+ }
+ else
+ {
+ excelPropety.ValueValidity(value, ErrorListVM.EntityList, rowIndex);
+ }
+
+ //如果没有错误,进行赋值
+ if (ErrorListVM.EntityList.Count == 0)
+ {
+ var pts = ListPropetys[pIndex];
+ pts.SetValue(result, excelPropety);
+ }
+
+ pIndex++;
+ }
+ result.ExcelIndex = rowIndex;
+ TemplateData.Add(result);
+ rowIndex++;
+ }
+
+ return;
+ }
+ catch
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = CoreProgram._localizer?["Sys.WrongTemplate"] });
+ }
+ }
+
+ #region 进行公式计算
+ public string GetCellFormulaValue(XSSFFormulaEvaluator XE, ICell cell, string Value)
+ {
+ if (!string.IsNullOrEmpty(Value) && Value.IndexOf("=") == 0)
+ {
+ try
+ {
+ string Formula = Value.Substring(1);
+ cell.SetCellFormula(Formula);
+ XE.EvaluateFormulaCell(cell);
+ Value = cell.NumericCellValue.ToString();
+ }
+ catch (Exception)
+ {
+ }
+ }
+ return Value;
+ }
+ #endregion
+
+ ///
+ /// 根据模板中的数据,填写导入类的集合中
+ ///
+ public virtual void SetEntityData()
+ {
+ //反射出类中所有属性字段 P是Model层定义的类
+ var pros = typeof(P).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
+
+ //反射出模板类中的所有属性字段 T是模板类,ExcelProperty 是自定义的Excel属性类
+ List ListExcelFields = typeof(T).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).Where(x => x.FieldType == typeof(ExcelPropety)).ToList();
+
+ //循环Excel中的数据
+ foreach (var item in TemplateData)
+ {
+ int rowIndex = 2;
+ bool isMainData = false;
+
+ //主表信息
+ Dictionary ParentEntity = new Dictionary();
+ string ParentEntityValues = string.Empty;
+
+ //子表信息
+ Dictionary> ChildrenEntity = new Dictionary>();
+ Dictionary ChildrenEntityDic = new Dictionary();
+
+ //循环TemplateVM中定义的所有的列,区分出主子表
+ foreach (var ExcelField in ListExcelFields)
+ {
+ //获取本列的ExcelProperty的值
+ if (typeof(T).GetField(ExcelField.Name).GetValue(item) is ExcelPropety ep)
+ {
+ //如果是子表的字段
+ if (ep.SubTableType != null)
+ {
+ //保存子表字段信息稍后处理
+ if (!ChildrenEntity.ContainsKey(ep.SubTableType))
+ {
+ ChildrenEntity[ep.SubTableType] = new List();
+ }
+ ChildrenEntity[ep.SubTableType].Add(ExcelField);
+ }
+ else
+ {
+ //保存子表字段信息稍后处理
+ ParentEntity.Add(ep.FieldName, ep);
+ ParentEntityValues += ep.Value;
+ }
+ }
+ }
+
+ //子表信息是否为空
+ foreach (var sub in ChildrenEntity)
+ {
+ string subVal = string.Empty;
+ foreach (var field in sub.Value)
+ {
+ ExcelPropety ep = typeof(T).GetField(field.Name).GetValue(item) as ExcelPropety;
+ subVal += ep.Value;
+ }
+ ChildrenEntityDic.Add(sub.Key, subVal);
+ }
+
+ P entity = null;
+
+ //说明主表信息为空
+ if (string.IsNullOrEmpty(ParentEntityValues))
+ {
+ entity = EntityList.LastOrDefault();
+ }
+ else
+ {
+ //初始化一个新的Entity
+ entity = new P();
+ isMainData = true;
+
+ //给主表赋值
+ foreach (var mep in ParentEntity)
+ {
+ SetEntityFieldValue(entity, mep.Value, rowIndex, mep.Key, item);
+ }
+ }
+
+ //给子表赋值
+ foreach (var sub in ChildrenEntity)
+ {
+ //循环Entity的所有属性,找到List类型的字段
+ foreach (var pro in pros)
+ {
+ if (pro.PropertyType.IsGenericType)
+ {
+ var gtype = pro.PropertyType.GetGenericArguments()[0];
+ if (gtype == sub.Key)
+ {
+ //子表
+ var subList = entity.GetType().GetSingleProperty(pro.Name).GetValue(entity);
+ string fk = DC.GetFKName(pro.Name);
+
+ //如果子表不为空
+ if (!string.IsNullOrEmpty(ChildrenEntityDic.Where(x => x.Key == sub.Key).FirstOrDefault().Value))
+ {
+ IList list = null;
+ if (subList == null)
+ {
+ //初始化List
+ list = typeof(List<>).MakeGenericType(gtype).GetConstructor(Type.EmptyTypes).Invoke(null) as IList;
+ }
+ else
+ {
+ list = subList as IList;
+ }
+
+ //初始化一个SubTableType
+ var SubTypeEntity = gtype.GetConstructor(System.Type.EmptyTypes).Invoke(null);
+
+ //给SubTableType中和本ExcelProperty同名的字段赋值
+ foreach (var field in sub.Value)
+ {
+ ExcelPropety ep = typeof(T).GetField(field.Name).GetValue(item) as ExcelPropety;
+ SetEntityFieldValue(SubTypeEntity, ep, rowIndex, ep.FieldName, item);
+ }
+
+ if (string.IsNullOrEmpty(fk) == false)
+ {
+ PropertyHelper.SetPropertyValue(SubTypeEntity, fk, entity.GetID());
+ }
+
+ if (typeof(IBasePoco).IsAssignableFrom(SubTypeEntity.GetType()))
+ {
+ (SubTypeEntity as IBasePoco).CreateTime = DateTime.Now;
+ (SubTypeEntity as IBasePoco).CreateBy = LoginUserInfo?.ITCode;
+ }
+ //var context = new ValidationContext(SubTypeEntity);
+ //var validationResults = new List();
+ //TryValidateObject(SubTypeEntity, context, validationResults);
+ //if (validationResults.Count == 0)
+ //{
+ //将付好值得SubTableType实例添加到List中
+ list.Add(SubTypeEntity);
+
+ PropertyHelper.SetPropertyValue(entity, pro.Name, list);
+ //}
+ //else
+ //{
+ // ErrorListVM.EntityList.Add(new ErrorMessage { Message = validationResults.FirstOrDefault()?.ErrorMessage ?? "Error", ExcelIndex = item.ExcelIndex });
+ // break;
+ //}
+
+ }
+ break;
+ }
+ }
+ }
+
+ }
+ entity.ExcelIndex = item.ExcelIndex;
+ if (isMainData)
+ {
+ EntityList.Add(entity);
+ }
+ }
+ }
+
+ ///
+ /// 进行上传中的错误验证
+ ///
+ public virtual void SetValidateCheck()
+ {
+ //找到对应的BaseCRUDVM,并初始化
+ var vms = this.GetType().Assembly.GetExportedTypes().Where(x => x.IsSubclassOf(typeof(BaseCRUDVM))).ToList();
+ var vmtype = vms.Where(x => x.Name.ToLower() == typeof(P).Name.ToLower() + "vm").FirstOrDefault();
+ if (vmtype == null)
+ {
+ vmtype = vms.FirstOrDefault();
+ }
+
+ IBaseCRUDVM
vm = null;
+ DuplicatedInfo
dinfo = null;
+ if (vmtype != null)
+ {
+ vm = vmtype.GetConstructor(System.Type.EmptyTypes).Invoke(null) as IBaseCRUDVM
;
+ vm.CopyContext(this);
+ dinfo = (vm as dynamic).SetDuplicatedCheck();
+ }
+ var cinfo = this.SetDuplicatedCheck();
+ finalInfo = new DuplicatedInfo
+ {
+ Groups = new List>()
+ };
+ if (cinfo != null)
+ {
+ foreach (var item in cinfo?.Groups)
+ {
+ finalInfo.Groups.Add(item);
+ }
+ }
+ else if (dinfo != null)
+ {
+ foreach (var item in dinfo?.Groups)
+ {
+ finalInfo.Groups.Add(item);
+ }
+ }
+ //调用controller方法验证model
+ //var vmethod = Controller?.GetType().GetMethod("RedoValidation");
+ foreach (var entity in EntityList)
+ {
+ //try
+ //{
+ // vmethod.Invoke(Controller, new object[] { entity });
+ //}
+ //catch { }
+
+ if (vm != null)
+ {
+ vm.SetEntity(entity);
+ vm.ByPassBaseValidation = true;
+ vm.Validate();
+ var basevm = vm as BaseVM;
+ if (basevm?.MSD?.Count > 0)
+ {
+ foreach (var key in basevm.MSD.Keys)
+ {
+ foreach (var error in basevm.MSD[key])
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = error.ErrorMessage, Index = entity.ExcelIndex });
+ }
+ }
+ }
+ }
+ (vm as BaseVM)?.MSD.Clear();
+
+ //在本地EntityList中验证是否有重复
+ ValidateDuplicateData(finalInfo, entity);
+ }
+ }
+
+ protected void SetEntityFieldValue(object entity, ExcelPropety ep, int rowIndex, string fieldName, T templateVM)
+ {
+ if (ep.FormatData != null)
+ {
+ ProcessResult processResult = ep.FormatData(ep.Value, templateVM);
+ if (processResult != null)
+ {
+ //未添加任何处理结果
+ if (processResult.EntityValues.Count == 0)
+ {
+ PropertyHelper.SetPropertyValue(entity, fieldName, ep.Value, stringBasedValue: true);
+ }
+ //字段为一对一
+ if (processResult.EntityValues.Count == 1)
+ {
+ ep.Value = processResult.EntityValues[0].FieldValue;
+ if (!string.IsNullOrEmpty(processResult.EntityValues[0].ErrorMsg))
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = processResult.EntityValues[0].ErrorMsg, ExcelIndex = rowIndex, Index = rowIndex });
+ }
+ PropertyHelper.SetPropertyValue(entity, fieldName, ep.Value, stringBasedValue: true);
+ }
+ //字段为一对多
+ if (processResult.EntityValues.Count > 1)
+ {
+ foreach (var entityValue in processResult.EntityValues)
+ {
+ if (!string.IsNullOrEmpty(entityValue.ErrorMsg))
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = entityValue.ErrorMsg, ExcelIndex = rowIndex, Index = rowIndex });
+ }
+ PropertyHelper.SetPropertyValue(entity, entityValue.FieldName, entityValue.FieldValue, stringBasedValue: true);
+ }
+ }
+ }
+ }
+ else if (ep.FormatSingleData != null)
+ {
+ ep.FormatSingleData(ep.Value, templateVM, out string singleEntityValue, out string errorMsg);
+ if (!string.IsNullOrEmpty(errorMsg))
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = errorMsg, ExcelIndex = rowIndex, Index = rowIndex });
+ }
+ PropertyHelper.SetPropertyValue(entity, fieldName, singleEntityValue, stringBasedValue: true);
+ }
+ else
+ {
+ PropertyHelper.SetPropertyValue(entity, fieldName, ep.Value, stringBasedValue: true);
+ }
+ }
+
+ protected bool IsUpdateRecordDuplicated(DuplicatedInfo checkCondition, P entity)
+ {
+ if (checkCondition != null && checkCondition.Groups.Count > 0)
+ {
+ //生成基础Query
+ var baseExp = EntityList.AsQueryable();
+ var modelType = typeof(P);
+ ParameterExpression para = Expression.Parameter(modelType, "tm");
+ //循环所有重复字段组
+ foreach (var group in checkCondition.Groups)
+ {
+ List conditions = new List();
+ //生成一个表达式,类似于 x=>x.Id != id,这是为了当修改数据时验证重复性的时候,排除当前正在修改的数据
+ var idproperty = modelType.GetSingleProperty("ID");
+ MemberExpression idLeft = Expression.Property(para, idproperty);
+ ConstantExpression idRight = Expression.Constant(entity.GetID());
+ BinaryExpression idNotEqual = Expression.NotEqual(idLeft, idRight);
+ conditions.Add(idNotEqual);
+ List props = new List();
+ //在每个组中循环所有字段
+ foreach (var field in group.Fields)
+ {
+ Expression exp = field.GetExpression(entity, para);
+ if (exp != null)
+ {
+ conditions.Add(exp);
+ }
+ //将字段名保存,为后面生成错误信息作准备
+ props.AddRange(field.GetProperties());
+ }
+ int count = 0;
+ if (conditions.Count > 1)
+ {
+ //循环添加条件并生成Where语句
+ Expression conExp = conditions[0];
+ for (int i = 1; i < conditions.Count; i++)
+ {
+ conExp = Expression.And(conExp, conditions[i]);
+ }
+
+ MethodCallExpression whereCallExpression = Expression.Call(
+ typeof(Queryable),
+ "Where",
+ new Type[] { modelType },
+ baseExp.Expression,
+ Expression.Lambda>(conExp, new ParameterExpression[] { para }));
+ var result = baseExp.Provider.CreateQuery(whereCallExpression);
+
+ foreach (var res in result)
+ {
+ count++;
+ }
+ }
+ if (count > 0)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ protected void ValidateDuplicateData(DuplicatedInfo checkCondition, P entity)
+ {
+ if (checkCondition != null && checkCondition.Groups.Count > 0)
+ {
+ //生成基础Query
+ var baseExp = EntityList.AsQueryable();
+ var modelType = typeof(P);
+ ParameterExpression para = Expression.Parameter(modelType, "tm");
+ //循环所有重复字段组
+ foreach (var group in checkCondition.Groups)
+ {
+ List conditions = new List();
+ //生成一个表达式,类似于 x=>x.Id != id,这是为了当修改数据时验证重复性的时候,排除当前正在修改的数据
+ var idproperty = modelType.GetSingleProperty("ExcelIndex");
+ MemberExpression idLeft = Expression.Property(para, idproperty);
+ ConstantExpression idRight = Expression.Constant(entity.ExcelIndex);
+ BinaryExpression idNotEqual = Expression.NotEqual(idLeft, idRight);
+ conditions.Add(idNotEqual);
+ List props = new List();
+ //在每个组中循环所有字段
+ foreach (var field in group.Fields)
+ {
+ Expression exp = field.GetExpression(entity, para);
+ if (exp != null)
+ {
+ conditions.Add(exp);
+ }
+ //将字段名保存,为后面生成错误信息作准备
+ props.AddRange(field.GetProperties());
+ }
+ int count = 0;
+ if (conditions.Count > 1)
+ {
+ //循环添加条件并生成Where语句
+ Expression whereCallExpression = baseExp.Expression;
+ for (int i = 0; i < conditions.Count; i++)
+ {
+ whereCallExpression = Expression.Call(
+ typeof(Queryable),
+ "Where",
+ new Type[] { modelType },
+ whereCallExpression,
+ Expression.Lambda>(conditions[i], new ParameterExpression[] { para }));
+ }
+ var result = baseExp.Provider.CreateQuery(whereCallExpression);
+
+ foreach (var res in result)
+ {
+ count++;
+ }
+ }
+ if (count > 0)
+ {
+ //循环拼接所有字段名
+ string AllName = "";
+ foreach (var prop in props)
+ {
+ string name = PropertyHelper.GetPropertyDisplayName(prop);
+ AllName += name + ",";
+ }
+ if (AllName.EndsWith(","))
+ {
+ AllName = AllName.Remove(AllName.Length - 1);
+ }
+ //如果只有一个字段重复,则拼接形成 xxx字段重复 这种提示
+ if (props.Count == 1)
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = CoreProgram._localizer?["Sys.DuplicateError", AllName], Index = entity.ExcelIndex });
+ }
+ //如果多个字段重复,则拼接形成 xx,yy,zz组合字段重复 这种提示
+ else if (props.Count > 1)
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = CoreProgram._localizer?["Sys.DuplicateGroupError", AllName], Index = entity.ExcelIndex });
+ }
+ }
+ }
+ }
+ }
+
+
+ private void TryValidateObject(object model, ValidationContext context, ICollection results)
+ {
+ var modelType = model.GetType();
+ foreach (var p in modelType.GetProperties())
+ {
+ var propertyValue = p.GetValue(model);
+ TryValidateProperty(propertyValue, context, results, p);
+ }
+ }
+
+ private void TryValidateProperty(object value, ValidationContext context, ICollection results, PropertyInfo propertyInfo = null)
+ {
+ var modelType = context.ObjectType;
+ if (propertyInfo == null)
+ {
+ propertyInfo = modelType.GetProperty(context.MemberName!);
+ }
+
+ if (propertyInfo != null)
+ {
+ var rules = propertyInfo.GetCustomAttributes(true).Where(i => i.GetType().BaseType == typeof(ValidationAttribute)).Cast();
+ var displayName = propertyInfo.GetPropertyDisplayName();
+ var memberName = propertyInfo.Name;
+ foreach (var rule in rules)
+ {
+ if (!rule.IsValid(value))
+ {
+ string errorMessage = "Error";
+ if (!string.IsNullOrEmpty(rule.ErrorMessage))
+ {
+ if (rule is RangeAttribute range)
+ {
+ if (range.Minimum != null && range.Maximum != null)
+ {
+ errorMessage = Wtm.Localizer[rule.ErrorMessage, displayName, range.Minimum, range.Maximum];
+ }
+ else if (range.Minimum != null)
+ {
+ errorMessage = Wtm.Localizer[rule.ErrorMessage, displayName, range.Minimum];
+ }
+ else if (range.Maximum != null)
+ {
+ errorMessage = Wtm.Localizer[rule.ErrorMessage, displayName, range.Maximum];
+ }
+ }
+ else if (rule is StringLengthAttribute sl)
+ {
+ if (sl.MaximumLength > 0 && sl.MinimumLength > 0)
+ {
+ errorMessage = Wtm.Localizer[rule.ErrorMessage, displayName, sl.MinimumLength, sl.MaximumLength];
+ }
+ else if (sl.MinimumLength > 0)
+ {
+ errorMessage = Wtm.Localizer[rule.ErrorMessage, displayName, sl.MinimumLength];
+ }
+ else if (sl.MaximumLength > 0)
+ {
+ errorMessage = Wtm.Localizer[rule.ErrorMessage, displayName, sl.MaximumLength];
+ }
+ }
+ else
+ {
+ errorMessage = Wtm.Localizer[rule.ErrorMessage, displayName];
+ }
+ }
+ results.Add(new ValidationResult(errorMessage, new string[] { memberName }));
+ }
+ }
+ }
+ }
+
+
+ ///
+ /// 保存指定表中的数据
+ ///
+ /// 成功返回True,失败返回False
+ public virtual bool BatchSaveData()
+ {
+ //删除不必要的附件
+ if (DeletedFileIds != null && DeletedFileIds.Count > 0 && Wtm.ServiceProvider != null)
+ {
+ var fp = Wtm.ServiceProvider.GetRequiredService();
+
+ foreach (var item in DeletedFileIds)
+ {
+ fp.DeleteFile(item.ToString(), DC.ReCreate());
+ }
+ }
+
+ //进行赋值
+ SetEntityList();
+ foreach (var entity in EntityList)
+ {
+ var context = new ValidationContext(entity);
+ var validationResults = new List();
+ TryValidateObject(entity, context, validationResults);
+ if (validationResults.Count > 0)
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = validationResults.FirstOrDefault()?.ErrorMessage ?? "Error", ExcelIndex = entity.ExcelIndex, Index = entity.ExcelIndex });
+ }
+ }
+ if (ErrorListVM.EntityList.Count > 0)
+ {
+ DoReInit();
+ return false;
+ }
+
+ //执行验证
+ SetValidateCheck();
+ if (ErrorListVM.EntityList.Count > 0)
+ {
+ DoReInit();
+ return false;
+ }
+
+ //循环数据列表
+ List ListAdd = new List
();
+ foreach (var item in EntityList)
+ {
+ //根据唯一性的设定查找数据库中是否有同样的数据
+ P exist = IsDuplicateData(item, finalInfo);
+ //如果设置了覆盖功能
+ if (IsOverWriteExistData)
+ {
+ if (exist != null)
+ {
+ //如果有重复数据,则进行修改
+ var tempPros = typeof(T).GetFields();
+ foreach (var pro in tempPros)
+ {
+ var excelProp = Template.GetType().GetField(pro.Name).GetValue(Template) as ExcelPropety;
+ var proToSet = typeof(P).GetSingleProperty(excelProp.FieldName);
+ if (proToSet != null)
+ {
+ var val = proToSet.GetValue(item);
+ PropertyHelper.SetPropertyValue(exist, excelProp.FieldName, val, stringBasedValue: true);
+ try
+ {
+ DC.UpdateProperty(exist, proToSet.Name);
+ }
+ catch { }
+ }
+ }
+
+ if (tempPros.Where(x => x.Name == "UpdateTime").SingleOrDefault() == null)
+ {
+ if (typeof(IBasePoco).IsAssignableFrom(exist.GetType()))
+ {
+ (exist as IBasePoco).UpdateTime = DateTime.Now;
+ DC.UpdateProperty(exist, "UpdateTime");
+ }
+ }
+
+ if (tempPros.Where(x => x.Name == "UpdateBy").SingleOrDefault() == null)
+ {
+ if (typeof(IBasePoco).IsAssignableFrom(exist.GetType()))
+ {
+ (exist as IBasePoco).UpdateBy = LoginUserInfo.ITCode;
+ DC.UpdateProperty(exist, "UpdateBy");
+ }
+ }
+ exist.ExcelIndex = item.ExcelIndex;
+ //DC.UpdateEntity(exist);
+
+ continue;
+ }
+ else
+ {
+ if (typeof(IPersistPoco).IsAssignableFrom(item.GetType()))
+ {
+ (item as IPersistPoco).IsValid = true;
+ }
+ }
+ }
+ else
+ {
+ if (exist == null)
+ {
+ if (typeof(IPersistPoco).IsAssignableFrom(item.GetType()))
+ {
+ (item as IPersistPoco).IsValid = true;
+ }
+ }
+ }
+ //进行添加操作
+ if (typeof(IBasePoco).IsAssignableFrom(item.GetType()))
+ {
+ (item as IBasePoco).CreateTime = DateTime.Now;
+ (item as IBasePoco).CreateBy = LoginUserInfo?.ITCode;
+ }
+ //如果是SqlServer数据库,而且没有主子表功能,进行Bulk插入
+ if (ConfigInfo.Connections.Where(x => x.Key == (CurrentCS ?? "default")).FirstOrDefault().DbType == DBTypeEnum.SqlServer && !HasSubTable && UseBulkSave == true)
+ {
+ ListAdd.Add(item);
+ }
+ else
+ {
+ DC.Set
().Add(item);
+ }
+ }
+
+ if (ErrorListVM.EntityList.Count > 0)
+ {
+ DoReInit();
+ return false;
+ }
+
+ //如果没有错误,更新数据库
+ if (EntityList.Count > 0)
+ {
+ try
+ {
+ DC.SaveChanges();
+
+ if (ListAdd.Count > 0)
+ {
+ BulkInsert
(DC, DC.GetTableName
(), ListAdd);
+ }
+ }
+ catch (Exception e)
+ {
+ SetExceptionMessage(e, null);
+ DoReInit();
+ return false;
+ }
+ }
+ if (string.IsNullOrEmpty(UploadFileId) == false && Wtm.ServiceProvider != null)
+ {
+ var fp = Wtm.ServiceProvider.GetRequiredService();
+ fp.DeleteFile(UploadFileId, DC.ReCreate());
+ }
+
+ return true;
+ }
+
+ ///
+ /// 批量插入数据库操作,支持SqlServer
+ ///
+ ///
+ /// data context
+ ///
+ ///
+ protected static void BulkInsert(IDataContext dc, string tableName, IList list)
+ {
+ using (var bulkCopy = new SqlBulkCopy(dc.CSName))
+ {
+ bulkCopy.BatchSize = list.Count;
+ bulkCopy.DestinationTableName = tableName;
+
+ var table = new DataTable();
+ var props = typeof(K).GetAllProperties().Distinct(x => x.Name);
+
+ //生成Table的列
+ foreach (var propertyInfo in props)
+ {
+ var notmapped = propertyInfo.GetCustomAttribute();
+ var notobject = propertyInfo.PropertyType.Namespace.Equals("System") || propertyInfo.PropertyType.IsEnumOrNullableEnum();
+ if (notmapped == null && notobject)
+ {
+ string Name = dc.GetFieldName(propertyInfo.Name);
+ bulkCopy.ColumnMappings.Add(Name, Name);
+ table.Columns.Add(Name, Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType);
+ }
+ }
+
+ //给Table赋值
+ var values = new object[table.Columns.Count];
+ foreach (var item in list)
+ {
+ var Index = 0;
+ foreach (var propertyInfo in props)
+ {
+ var notmapped = propertyInfo.GetCustomAttribute();
+ var notobject = propertyInfo.PropertyType.Namespace.Equals("System") || propertyInfo.PropertyType.IsEnumOrNullableEnum();
+ if (notmapped == null && notobject)
+ {
+ values[Index] = propertyInfo.GetValue(item);
+ Index++;
+ }
+ }
+ table.Rows.Add(values);
+ }
+ //检测是否有继承字段,如果存在,进行赋值
+ string Discriminator = dc.GetFieldName("Discriminator");
+ if (!string.IsNullOrEmpty(Discriminator))
+ {
+ bulkCopy.ColumnMappings.Add("Discriminator", "Discriminator");
+ table.Columns.Add("Discriminator", typeof(string));
+ for (int i = 0; i < table.Rows.Count; i++)
+ {
+ table.Rows[i]["Discriminator"] = typeof(K).Name;
+ }
+ }
+ bulkCopy.WriteToServer(table);
+ }
+ }
+ #endregion
+
+ #region 验证是否空行
+ ///
+ /// 验证Excel中某行是否为空行
+ ///
+ /// 行数
+ /// 列数
+ /// True代表空行,False代表非空行
+ private bool IsEmptyRow(XSSFRow row, int colCount)
+ {
+ bool result = true;
+ for (int i = 0; i < colCount; i++)
+ {
+ string value = row.GetCell(i, MissingCellPolicy.CREATE_NULL_AS_BLANK).ToString();
+ if (!string.IsNullOrEmpty(value))
+ {
+ result = false;
+ break;
+ }
+ }
+ return result;
+ }
+ #endregion
+
+ #region 复制Excel属性
+ ///
+ /// 复制Excel属性
+ ///
+ /// 单元格属性
+ /// 复制后的单元格
+ private ExcelPropety CopyExcelPropety(ExcelPropety excelPropety)
+ {
+ ExcelPropety ep = new ExcelPropety
+ {
+ BackgroudColor = excelPropety.BackgroudColor,
+ ColumnName = excelPropety.ColumnName,
+ DataType = excelPropety.DataType,
+ ResourceType = excelPropety.ResourceType,
+ IsNullAble = excelPropety.IsNullAble,
+ ListItems = excelPropety.ListItems,
+ MaxValuseOrLength = excelPropety.MaxValuseOrLength,
+ MinValueOrLength = excelPropety.MinValueOrLength,
+ Value = excelPropety.Value,
+ SubTableType = excelPropety.SubTableType,
+ CharCount = excelPropety.CharCount,
+ ReadOnly = excelPropety.ReadOnly,
+ FormatData = excelPropety.FormatData,
+ FormatSingleData = excelPropety.FormatSingleData,
+ FieldName = excelPropety.FieldName
+ };
+ List li = new List();
+ foreach (var item in excelPropety.DynamicColumns)
+ {
+ li.Add(CopyExcelPropety(item));
+ }
+ ep.DynamicColumns = li;
+ return ep;
+ }
+ #endregion
+
+ #region 设置异常信息
+ ///
+ /// 设置错误信息
+ ///
+ /// 异常
+ /// 数据Id
+ protected void SetExceptionMessage(Exception e, long? id)
+ {
+ //检查是否为数据库操作错误
+ if (e is DbUpdateException)
+ {
+ var de = e as DbUpdateException;
+ if (de.Entries != null)
+ {
+ if (de.Entries.Count == 0)
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Index = 0, Message = e.Message + e.InnerException?.Message });
+ }
+ //循环此错误相关的数据
+ foreach (var ent in de.Entries)
+ {
+ //获取错误数据Id
+ var errorId = (long)((ent.Entity as TopBasePoco).ExcelIndex);
+ //根据State判断修改或删除操作,输出不同的错误信息
+ if (ent.State == EntityState.Deleted)
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Index = errorId, Message = CoreProgram._localizer?["Sys.DataCannotDelete"] });
+ }
+ else if (ent.State == EntityState.Modified)
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Index = errorId, Message = CoreProgram._localizer?["Sys.EditFailed"] });
+ }
+ else
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Index = errorId, Message = de.Message });
+ }
+ }
+ }
+ }
+ //对于其他类型的错误,直接添加错误信息
+ else
+ {
+ if (id != null)
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Index = id.Value, Message = e.Message });
+ }
+ else
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Index = 0, Message = e.Message });
+ }
+ }
+ }
+ #endregion
+
+ #region 验证数据重复
+
+ ///
+ /// 判断数据是否在库中存在重复数据
+ ///
+ /// 要验证的数据
+ /// 验证表达式
+ /// null代表没有重复
+ protected P IsDuplicateData(P Entity, DuplicatedInfo checkCondition)
+ {
+ //获取设定的重复字段信息
+ if (checkCondition != null && checkCondition.Groups.Count > 0)
+ {
+ //生成基础Query
+ var baseExp = DC.Set
().AsQueryable();
+ var modelType = typeof(P);
+ ParameterExpression para = Expression.Parameter(modelType, "tm");
+ //循环所有重复字段组
+ foreach (var group in checkCondition.Groups)
+ {
+ List conditions = new List();
+ //生成一个表达式,类似于 x=>x.Id != id,这是为了当修改数据时验证重复性的时候,排除当前正在修改的数据
+ //在每个组中循环所有字段
+ List props = new List();
+ //在每个组中循环所有字段
+ foreach (var field in group.Fields)
+ {
+ Expression exp = field.GetExpression(Entity, para);
+ if (exp != null)
+ {
+ conditions.Add(exp);
+ }
+ //将字段名保存,为后面生成错误信息作准备
+ props.AddRange(field.GetProperties());
+ }
+
+ if (conditions.Count > 0)
+ {
+ //循环添加条件并生成Where语句
+ Expression whereCallExpression = baseExp.Expression;
+ for (int i = 0; i < conditions.Count; i++)
+ {
+ whereCallExpression = Expression.Call(
+ typeof(Queryable),
+ "Where",
+ new Type[] { modelType },
+ whereCallExpression,
+ Expression.Lambda>(conditions[i], new ParameterExpression[] { para }));
+ }
+ var result = baseExp.Provider.CreateQuery(whereCallExpression);
+
+
+ foreach (var res in result)
+ {
+ if (IsOverWriteExistData == false)
+ {
+ //循环拼接所有字段名
+ string AllName = "";
+ foreach (var prop in props)
+ {
+ string name = PropertyHelper.GetPropertyDisplayName(prop);
+ AllName += name + ",";
+ }
+ if (AllName.EndsWith(","))
+ {
+ AllName = AllName.Remove(AllName.Length - 1);
+ }
+ //如果只有一个字段重复,则拼接形成 xxx字段重复 这种提示
+ if (props.Count == 1)
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = CoreProgram._localizer?["Sys.DuplicateError", AllName], Index = Entity.ExcelIndex });
+ }
+ //如果多个字段重复,则拼接形成 xx,yy,zz组合字段重复 这种提示
+ else if (props.Count > 1)
+ {
+ ErrorListVM.EntityList.Add(new ErrorMessage { Message = CoreProgram._localizer?["Sys.DuplicateGroupError", AllName], Index = Entity.ExcelIndex });
+ }
+
+ }
+ return res as P;
+ }
+ }
+ }
+ }
+ return null;
+ }
+ #endregion
+
+ protected DuplicatedInfo CreateFieldsInfo(params DuplicatedField
[] FieldExps)
+ {
+ DuplicatedInfo
d = new DuplicatedInfo
();
+ d.AddGroup(FieldExps);
+ return d;
+ }
+
+ ///
+ /// 创建一个简单重复数据信息
+ ///
+ /// 重复数据的字段
+ /// 重复数据信息
+ public static DuplicatedField
SimpleField(Expression> FieldExp)
+ {
+ return new DuplicatedField(FieldExp);
+ }
+
+ ///
+ /// 创建一个关联到其他表数组中数据的重复信息
+ ///
+ /// 关联表类
+ /// 指向关联表类数组的Lambda
+ /// 指向最终字段的Lambda
+ /// 重复数据信息
+ public static DuplicatedField
SubField(Expression>> MiddleExp, params Expression>[] FieldExps)
+ {
+ return new ComplexDuplicatedField(MiddleExp, FieldExps);
+ }
+
+ public ErrorObj GetErrorJson()
+ {
+ var mse = new ErrorObj();
+ mse.Form = new Dictionary();
+ var err = ErrorListVM?.EntityList?.Where(x => x.Index == 0).FirstOrDefault()?.Message;
+ if (string.IsNullOrEmpty(err))
+ {
+ Models.IWtmFile fa = null;
+ if(Wtm.ServiceProvider == null) {
+ return mse;
+ }
+ var fp = Wtm.ServiceProvider.GetRequiredService();
+ fa = fp.GetFile(UploadFileId, true, DC);
+ xssfworkbook = new XSSFWorkbook(fa.DataStream);
+ fa.DataStream.Dispose();
+ var propetys = Template.GetType().GetFields().Where(x => x.FieldType == typeof(ExcelPropety)).ToList();
+ List excelPropetys = new List();
+ for (int porpetyIndex = 0; porpetyIndex < propetys.Count(); porpetyIndex++)
+ {
+ ExcelPropety ep = (ExcelPropety)propetys[porpetyIndex].GetValue(Template);
+ excelPropetys.Add(ep);
+ }
+ int columnCount = excelPropetys.Count;
+ //int excelPropetyCount = excelPropetys.Count;
+ var dynamicColumn = excelPropetys.Where(x => x.DataType == ColumnDataType.Dynamic).FirstOrDefault();
+ if (dynamicColumn != null)
+ {
+ columnCount = columnCount + dynamicColumn.DynamicColumns.Count - 1;
+ }
+ ISheet sheet = xssfworkbook.GetSheetAt(0);
+ var errorStyle = xssfworkbook.CreateCellStyle();
+ IFont f = xssfworkbook.CreateFont();
+ f.Color = HSSFColor.Red.Index;
+ errorStyle.SetFont(f);
+ errorStyle.IsLocked = true;
+ foreach (var e in ErrorListVM?.EntityList)
+ {
+ if (e.Index > 0)
+ {
+ var c = sheet.GetRow((int)(e.Index - 1)).CreateCell(columnCount);
+ c.CellStyle = errorStyle;
+ c.SetCellValue(e.Message);
+ }
+ }
+ MemoryStream ms = new MemoryStream();
+ xssfworkbook.Write(ms);
+ ms.Position = 0;
+
+ var newfile = fp.Upload("Error-" + fa.FileName, ms.Length, ms);
+ ms.Close();
+ ms.Dispose();
+ err = CoreProgram._localizer?["Sys.ImportError"];
+ mse.Form.Add("Entity.Import", err);
+ mse.Form.Add("Entity.ErrorFileId", newfile.GetID());
+ }
+ else
+ {
+ mse.Form.Add("Entity.Import", err);
+ }
+ return mse;
+ }
+ }
+
+ #region 辅助类
+ public class ErrorMessage : TopBasePoco
+ {
+ [Display(Name = "Sys.RowIndex")]
+ public long Index { get; set; }
+
+ [Display(Name = "Sys.CellIndex")]
+ public long Cell { get; set; }
+ [Display(Name = "Sys.ErrorMsg")]
+ public string Message { get; set; }
+ }
+
+ ///
+ /// 错误数据列表
+ ///
+ public class TemplateErrorListVM : BasePagedListVM
+ {
+
+ public TemplateErrorListVM()
+ {
+ EntityList = new List();
+ NeedPage = false;
+ }
+
+ protected override IEnumerable> InitGridHeader()
+ {
+ return new List>{
+ this.MakeGridHeader(x => x.Index, 60),
+ this.MakeGridHeader(x => x.Message)
+ };
+ }
+
+ public override IOrderedQueryable GetSearchQuery()
+ {
+ return EntityList.AsQueryable().OrderBy(x => x.Index);
+ }
+ }
+
+ #endregion
+
+}
diff --git a/WalkingTec.Mvvm/WalkingTec.Mvvm.Core/BasePagedListVM.cs b/WalkingTec.Mvvm/WalkingTec.Mvvm.Core/BasePagedListVM.cs
new file mode 100644
index 0000000..42ef418
--- /dev/null
+++ b/WalkingTec.Mvvm/WalkingTec.Mvvm.Core/BasePagedListVM.cs
@@ -0,0 +1,1228 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Data;
+using System.Data.Common;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using Microsoft.Data.SqlClient;
+using Microsoft.EntityFrameworkCore;
+using MySqlConnector;
+using Npgsql;
+using NpgsqlTypes;
+using NPOI.HSSF.Util;
+using NPOI.SS.UserModel;
+using NPOI.SS.Util;
+using NPOI.XSSF.UserModel;
+using WalkingTec.Mvvm.Core.Extensions;
+
+namespace WalkingTec.Mvvm.Core
+{
+ public delegate object ColumnFormatCallBack(T entity, object fieldValue) where T : TopBasePoco;
+
+ ///
+ /// ListVM的搜索模式枚举
+ ///
+ public enum ListVMSearchModeEnum
+ {
+ Search, //搜索
+ Export, //导出
+ Batch, //批量
+ Selector,//选择器
+ MasterDetail, //
+ CheckExport,
+ Custom1, Custom2, Custom3, Custom4, Custom5
+ };
+
+ ///
+ /// ListVM的基类,所有ListVM应该继承这个类, 基类提供了搜索,导出等列表常用功能
+ ///
+ /// ListVM中的Model类
+ /// ListVM使用的Searcher类
+ public class BasePagedListVM : BaseVM, IBasePagedListVM
+ where TModel : TopBasePoco
+ where TSearcher : BaseSearcher
+ {
+
+
+ [JsonIgnore]
+ public string TotalText { get; set; } = CoreProgram._localizer?["Sys.Total"];
+
+ public virtual DbCommand GetSearchCommand()
+ {
+ return null;
+ }
+
+ private int? _childrenDepth;
+
+
+ ///
+ /// 多级表头深度 默认 1级
+ ///
+ public int GetChildrenDepth()
+ {
+ if (_childrenDepth == null)
+ {
+ _childrenDepth = _getHeaderDepth();
+ }
+ return _childrenDepth.Value;
+ }
+
+ ///
+ /// GridHeaders
+ ///
+ [JsonIgnore]
+ private IEnumerable> GridHeaders { get; set; }
+
+ ///
+ /// GetHeaders
+ ///
+ ///
+ public IEnumerable> GetHeaders()
+ {
+ if (GridHeaders == null)
+ {
+ GridHeaders = InitGridHeader();
+ }
+ return GridHeaders;
+ }
+
+ ///
+ /// 计算多级表头深度
+ ///
+ ///
+ private int _getHeaderDepth()
+ {
+ IEnumerable> headers = GetHeaders();
+ return headers.Max(x => x.MaxDepth);
+ }
+
+ private List _gridActions;
+
+ ///
+ /// 页面动作
+ ///
+ public List GetGridActions()
+ {
+ if (_gridActions == null)
+ {
+ _gridActions = InitGridAction();
+ }
+ return _gridActions;
+ }
+
+ ///
+ /// 初始化 InitGridHeader,继承的类应该重载这个函数来设定数据的列和动作
+ ///
+ protected virtual IEnumerable> InitGridHeader()
+ {
+ return new List>();
+ }
+
+ protected virtual List InitGridAction()
+ {
+ return new List();
+ }
+
+ #region GenerateExcel
+
+ ///
+ /// 生成Excel
+ ///
+ /// 生成的Excel文件
+ public virtual byte[] GenerateExcel()
+ {
+ NeedPage = false;
+
+ //获取导出的表头
+ if (GridHeaders == null)
+ {
+ GetHeaders();
+ }
+
+ //去掉ID列和Action列
+ RemoveActionAndIdColumn();
+
+ //如果没有数据源,进行查询
+ if (IsSearched == false)
+ {
+ DoSearch();
+ }
+
+ //获取分成Excel的个数
+ ExportMaxCount = ExportMaxCount == 0 ? 1000000 : (ExportMaxCount > 1000000 ? 1000000 : ExportMaxCount);
+ ExportExcelCount = EntityList.Count < ExportMaxCount ? 1 : ((EntityList.Count % ExportMaxCount) == 0 ? (EntityList.Count / ExportMaxCount) : (EntityList.Count / ExportMaxCount + 1));
+
+ //如果是1,直接下载Excel,如果是多个,下载ZIP包
+ if (ExportExcelCount == 1)
+ {
+ return DownLoadExcel();
+ }
+ else
+ {
+ return DownLoadZipPackage(typeof(TModel).Name + "_" + DateTime.Now.ToString("yyyyMMddHHmmssffff"));
+ }
+ }
+
+ ///
+ /// 根据集合生成单个Excel
+ ///
+ ///
+ ///
+ private IWorkbook GenerateWorkBook(List List)
+ {
+ IWorkbook book = new XSSFWorkbook();
+ ISheet sheet = book.CreateSheet();
+ IRow row = sheet.CreateRow(0);
+
+ //创建表头样式
+ ICellStyle headerStyle = book.CreateCellStyle();
+ headerStyle.FillBackgroundColor = ExportTitleBackColor == null ? HSSFColor.LightBlue.Index : ExportTitleBackColor.Value;
+ headerStyle.FillPattern = FillPattern.SolidForeground;
+ headerStyle.FillForegroundColor = ExportTitleBackColor == null ? HSSFColor.LightBlue.Index : ExportTitleBackColor.Value;
+ headerStyle.BorderBottom = BorderStyle.Thin;
+ headerStyle.BorderTop = BorderStyle.Thin;
+ headerStyle.BorderLeft = BorderStyle.Thin;
+ headerStyle.BorderRight = BorderStyle.Thin;
+ IFont font = book.CreateFont();
+ font.FontName = "Calibri";
+ font.FontHeightInPoints = 12;
+ font.Color = ExportTitleFontColor == null ? HSSFColor.Black.Index : ExportTitleFontColor.Value;
+ headerStyle.SetFont(font);
+
+ ICellStyle cellStyle = book.CreateCellStyle();
+ cellStyle.BorderBottom = BorderStyle.Thin;
+ cellStyle.BorderTop = BorderStyle.Thin;
+ cellStyle.BorderLeft = BorderStyle.Thin;
+ cellStyle.BorderRight = BorderStyle.Thin;
+
+ //生成表头
+ int max = MakeExcelHeader(sheet, GridHeaders, 0, 0, headerStyle);
+
+ //放入数据
+ var ColIndex = 0;
+ for (int i = 0; i < List.Count; i++)
+ {
+ ColIndex = 0;
+ var DR = sheet.CreateRow(i + max);
+ foreach (var baseCol in GridHeaders)
+ {
+ foreach (var col in baseCol.BottomChildren)
+ {
+ //处理枚举变量的多语言
+ bool IsEmunBoolParp = false;
+ var proType = col.FieldType;
+ if (proType.IsEnumOrNullableEnum())
+ {
+ IsEmunBoolParp = true;
+ } //获取数据,并过滤特殊字符
+ string text = Regex.Replace(col.GetText(List[i]).ToString(), @"<[^>]*>", String.Empty);
+
+ //处理枚举变量的多语言
+ if (IsEmunBoolParp)
+ {
+ string enumdisplay = PropertyHelper.GetEnumDisplayName(proType, text);
+ if (string.IsNullOrEmpty(enumdisplay) == false)
+ {
+ text = enumdisplay;
+ }
+
+ else
+ {
+ if (int.TryParse(text, out int enumvalue))
+ {
+ text = PropertyHelper.GetEnumDisplayName(proType, enumvalue);
+ }
+ }
+ }
+
+ //建立excel单元格
+ ICell cell;
+ if (col.FieldType?.IsNumber() == true)
+ {
+ cell = DR.CreateCell(ColIndex, CellType.Numeric);
+ try
+ {
+ cell.SetCellValue(Convert.ToDouble(text));
+ }
+ catch { }
+ }
+ else
+ {
+ cell = DR.CreateCell(ColIndex);
+ cell.SetCellValue(text);
+ }
+ cell.CellStyle = cellStyle;
+ ColIndex++;
+ }
+ }
+ }
+ return book;
+ }
+
+ private byte[] DownLoadExcel()
+ {
+ var book = GenerateWorkBook(EntityList);
+ byte[] rv = new byte[] { };
+ using (MemoryStream ms = new MemoryStream())
+ {
+ book.Write(ms);
+ rv = ms.ToArray();
+ }
+ return rv;
+ }
+
+ private byte[] DownLoadZipPackage(string FileName)
+ {
+ //文件根目录
+ string RootPath = $"{Directory.GetCurrentDirectory()}\\{FileName}";
+
+ //文件夹目录
+ string FilePath = $"{RootPath}//FileFolder";
+
+ //压缩包目录
+ string ZipPath = $"{RootPath}//{FileName}.zip";
+
+ //打开文件夹
+ DirectoryInfo FileFolder = new DirectoryInfo(FilePath);
+ if (!FileFolder.Exists)
+ {
+ //创建文件夹
+ FileFolder.Create();
+ }
+ else
+ {
+ //清空文件夹
+ FileSystemInfo[] Files = FileFolder.GetFileSystemInfos();
+ foreach (var item in Files)
+ {
+ if (item is DirectoryInfo)
+ {
+ DirectoryInfo Directory = new DirectoryInfo(item.FullName);
+ Directory.Delete(true);
+ }
+ else
+ {
+ File.Delete(item.FullName);
+ }
+ }
+ }
+
+ //放入数据
+ for (int i = 0; i < ExportExcelCount; i++)
+ {
+ var List = EntityList.Skip(i * ExportMaxCount).Take(ExportMaxCount).ToList();
+ var WorkBook = GenerateWorkBook(List);
+ string SavePath = $"{FilePath}/{FileName}_{i + 1}.xlsx";
+ using (FileStream FS = new FileStream(SavePath, FileMode.CreateNew))
+ {
+ WorkBook.Write(FS);
+ }
+ }
+
+ //生成压缩包
+ ZipFile.CreateFromDirectory(FilePath, ZipPath);
+
+ //读取压缩包
+ FileStream ZipFS = new FileStream(ZipPath, FileMode.Open, FileAccess.Read);
+ byte[] bt = new byte[ZipFS.Length];
+ ZipFS.Read(bt, 0, bt.Length);
+ ZipFS.Close();
+
+ //删除根目录文件夹
+ DirectoryInfo RootFolder = new DirectoryInfo(RootPath);
+ if (RootFolder.Exists)
+ {
+ RootFolder.Delete(true);
+ }
+
+ return bt;
+ }
+
+ ///
+ /// 生成Excel的表头
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private int MakeExcelHeader(ISheet sheet, IEnumerable> cols, int rowIndex, int colIndex, ICellStyle style)
+ {
+ var row = sheet.GetRow(rowIndex);
+ if (row == null)
+ {
+ row = sheet.CreateRow(rowIndex);
+ }
+ int maxLevel = cols.Select(x => x.MaxLevel).Max();
+ //循环所有列
+ foreach (var col in cols)
+ {
+ //添加新单元格
+ var cell = row.CreateCell(colIndex);
+ cell.CellStyle = style;
+ cell.SetCellValue(col.Title);
+ var bcount = col.BottomChildren.Count();
+ var rowspan = 0;
+ if (rowIndex >= 0)
+ {
+ rowspan = maxLevel - col.MaxLevel;
+ }
+ var cellRangeAddress = new CellRangeAddress(rowIndex, rowIndex + rowspan, colIndex, colIndex + bcount - 1);
+ sheet.AddMergedRegion(cellRangeAddress);
+ if (rowspan > 0 || bcount > 1)
+ {
+ cell.CellStyle.Alignment = HorizontalAlignment.Center;
+ cell.CellStyle.VerticalAlignment = VerticalAlignment.Center;
+ }
+ for (int i = cellRangeAddress.FirstRow; i <= cellRangeAddress.LastRow; i++)
+ {
+ IRow r = CellUtil.GetRow(i, sheet);
+ for (int j = cellRangeAddress.FirstColumn; j <= cellRangeAddress.LastColumn; j++)
+ {
+ ICell c = CellUtil.GetCell(r, (short)j);
+ c.CellStyle = style;
+ }
+ }
+ if (col.Children != null && col.Children.Count() > 0)
+ {
+ MakeExcelHeader(sheet, col.Children, rowIndex + rowspan + 1, colIndex, style);
+ }
+ colIndex += bcount;
+ }
+ return maxLevel;
+ }
+
+ #endregion
+
+ #region Old
+ public SortInfo CreateSortInfo(Expression> pro, SortDir dir)
+ {
+ SortInfo rv = new SortInfo
+ {
+ Property = PropertyHelper.GetPropertyName(pro),
+ Direction = dir
+ };
+ return rv;
+ }
+
+ ///
+ /// InitList后触发的事件
+ ///
+ public event Action> OnAfterInitList;
+
+ ///
+ ///记录批量操作时列表中选择的Id
+ ///
+ public List Ids { get; set; }
+ public string SelectorValueField { get; set; }
+ ///
+ /// 是否已经搜索过
+ ///
+ [JsonIgnore]
+ public bool IsSearched { get; set; }
+
+ [JsonIgnore]
+ public bool PassSearch { get; set; }
+ ///
+ /// 查询模式
+ ///
+ [JsonIgnore]
+ public ListVMSearchModeEnum SearcherMode { get; set; }
+
+ ///
+ /// 是否需要分页
+ ///
+ [JsonIgnore]
+ public bool NeedPage { get; set; }
+
+ ///
+ /// 允许导出Excel的最大行数,超过行数会分成多个文件,最多不能超过100万
+ ///
+ [JsonIgnore]
+ public int ExportMaxCount { get; set; }
+
+ ///
+ /// 根据允许导出的Excel最大行数,算出最终导出的Excel个数
+ ///
+ [JsonIgnore]
+ public int ExportExcelCount { get; set; }
+
+ ///
+ /// 导出文件第一行背景颜色,使用HSSFColor,例如:HSSFColor.Red.Index
+ ///
+ [JsonIgnore]
+ public short? ExportTitleBackColor { get; set; }
+
+ ///
+ /// 导出文件第一行文字颜色,使用HSSFColor,例如:HSSFColor.Red.Index
+ ///
+ [JsonIgnore]
+ public short? ExportTitleFontColor { get; set; }
+
+ ///
+ /// 数据列表
+ ///
+ [JsonIgnore]
+ public List EntityList { get; set; }
+
+
+ ///
+ /// 搜索条件
+ ///
+ [JsonIgnore]
+ public TSearcher Searcher { get; set; }
+
+ ///
+ /// 使用 VM 的 Id 来生成 SearcherDiv 的 Id
+ ///
+ [JsonIgnore]
+ public string SearcherDivId
+ {
+ get { return this.UniqueId + "Searcher"; }
+ }
+
+
+ ///
+ /// 替换查询条件,如果被赋值,则列表会使用里面的Lambda来替换原有Query里面的Where条件
+ ///
+ [JsonIgnore()]
+ public Expression ReplaceWhere { get; set; }
+
+ ///
+ /// 构造函数
+ ///
+ public BasePagedListVM()
+ {
+ //默认需要分页
+ NeedPage = true;
+ //初始化数据列表
+ EntityList = new List();
+ //初始化搜索条件
+ Searcher = typeof(TSearcher).GetConstructor(Type.EmptyTypes).Invoke(null) as TSearcher;
+ }
+
+ ///
+ /// 获取数据列表
+ ///
+ /// 数据列表
+ public IEnumerable GetEntityList()
+ {
+ if (IsSearched == false && (EntityList == null || EntityList.Count == 0))
+ {
+ DoSearch();
+ }
+ return EntityList?.AsEnumerable();
+ }
+
+
+ ///
+ /// 调用InitListVM并触发OnAfterInitList事件
+ ///
+ public void DoInitListVM()
+ {
+ InitListVM();
+ OnAfterInitList?.Invoke(this);
+ }
+
+
+ ///
+ /// 初始化ListVM,继承的类应该重载这个函数来设定数据的列和动作
+ ///
+ protected virtual void InitListVM()
+ {
+ }
+
+ public virtual bool GetIsSelected(object item)
+ {
+ return false;
+ }
+
+ public override void Validate()
+ {
+ Searcher?.Validate();
+ base.Validate();
+ }
+
+ ///
+ /// 设定行前景色,继承的类应重载这个函数来根据每行的数据显示不同的前景色
+ ///
+ /// 数据
+ /// 前景颜色
+ public virtual string SetFullRowColor(object entity)
+ {
+ return "";
+ }
+
+ ///
+ /// 设定行背景色,继承的类应重载这个函数来根据每行的数据显示不同的背景色
+ ///
+ /// 数据
+ /// 背景颜色
+ public virtual string SetFullRowBgColor(object entity)
+ {
+ return "";
+ }
+
+ ///
+ /// 设定搜索语句,继承的类应该重载这个函数来指定自己的搜索语句
+ ///
+ /// 搜索语句
+ public virtual IOrderedQueryable GetSearchQuery()
+ {
+ return DC.Set().OrderByDescending(x => x.ID);
+ }
+
+ ///
+ /// 设定导出时搜索语句,继承的类应该重载这个函数来指定自己导出时的搜索语句,如不指定则默认和搜索用的搜索语句相同
+ ///
+ /// 搜索语句
+ public virtual IOrderedQueryable GetExportQuery()
+ {
+ return GetSearchQuery();
+ }
+
+ ///
+ /// 设定搜索语句,继承的类应该重载这个函数来指定自己导出时的搜索语句,如不指定则默认和搜索用的搜索语句相同
+ ///
+ /// 搜索语句
+ public virtual IOrderedQueryable GetSelectorQuery()
+ {
+ return GetSearchQuery();
+ }
+
+ ///
+ /// 设定勾选后导出的搜索语句,继承的类应该重载这个函数来指定自己导出时的搜索语句,如不指定则默认和搜索用的搜索语句相同
+ ///
+ /// 搜索语句
+ public virtual IOrderedQueryable GetCheckedExportQuery()
+ {
+ var baseQuery = GetBatchQuery();
+ return baseQuery;
+ }
+
+ ///
+ /// 设定批量模式下的搜索语句,继承的类应重载这个函数来指定自己批量模式的搜索语句,如果不指定则默认使用Ids.Contains(x.Id)来代替搜索语句中的Where条件
+ ///
+ /// 搜索语句
+ public virtual IOrderedQueryable GetBatchQuery()
+ {
+ var baseQuery = GetSearchQuery();
+ if (ReplaceWhere == null)
+ {
+ var mod = new WhereReplaceModifier(Ids.GetContainIdExpression());
+ var newExp = mod.Modify(baseQuery.Expression);
+ var newQuery = baseQuery.Provider.CreateQuery(newExp) as IOrderedQueryable;
+ return newQuery;
+ }
+ else
+ {
+ return baseQuery;
+ }
+ }
+
+ ///
+ /// 设定主从模式的搜索语句,继承的类应该重载这个函数来指定自己主从模式的搜索语句,如不指定则默认和搜索用的搜索语句相同
+ ///
+ /// 搜索语句
+ public virtual IOrderedQueryable GetMasterDetailsQuery()
+ {
+ return GetSearchQuery();
+ }
+
+ ///
+ /// 进行搜索
+ ///
+ public virtual void DoSearch()
+ {
+ var cmd = GetSearchCommand();
+ if (cmd == null)
+ {
+ IOrderedQueryable query = null;
+ //根据搜索模式调用不同的函数
+ switch (SearcherMode)
+ {
+ case ListVMSearchModeEnum.Search:
+ query = GetSearchQuery();
+ break;
+ case ListVMSearchModeEnum.Export:
+ query = GetExportQuery();
+ break;
+ case ListVMSearchModeEnum.Batch:
+ query = GetBatchQuery();
+ break;
+ case ListVMSearchModeEnum.MasterDetail:
+ query = GetMasterDetailsQuery();
+ break;
+ case ListVMSearchModeEnum.CheckExport:
+ query = GetCheckedExportQuery();
+ break;
+ case ListVMSearchModeEnum.Selector:
+ query = GetSelectorQuery();
+ break;
+ default:
+ query = GetSearchQuery();
+ break;
+ }
+
+ //如果设定了替换条件,则使用替换条件替换Query中的Where语句
+ if (ReplaceWhere != null)
+ {
+ var mod = new WhereReplaceModifier(ReplaceWhere as Expression>);
+ var newExp = mod.Modify(query.Expression);
+ query = query.Provider.CreateQuery(newExp) as IOrderedQueryable;
+ }
+ if (Searcher.SortInfo != null)
+ {
+ var mod = new OrderReplaceModifier(Searcher.SortInfo);
+ var newExp = mod.Modify(query.Expression);
+ query = query.Provider.CreateQuery(newExp) as IOrderedQueryable;
+ }
+ if (typeof(IPersistPoco).IsAssignableFrom( typeof(TModel)))
+ {
+ var mod = new IsValidModifier();
+ var newExp = mod.Modify(query.Expression);
+ query = query.Provider.CreateQuery(newExp) as IOrderedQueryable;
+ }
+ if (PassSearch == false)
+ {
+ //如果需要分页,则添加分页语句
+ if (NeedPage && Searcher.Limit != -1)
+ {
+ //获取返回数据的数量
+ var count = query.Count();
+ if (count < 0)
+ {
+ count = 0;
+ }
+ if (Searcher.Limit == 0)
+ {
+ Searcher.Limit = ConfigInfo?.UIOptions.DataTable.RPP ?? 20;
+ }
+ //根据返回数据的数量,以及预先设定的每页行数来设定数据量和总页数
+ Searcher.Count = count;
+ Searcher.PageCount = (int)Math.Ceiling((1.0 * Searcher.Count / Searcher.Limit));
+ if (Searcher.Page <= 0)
+ {
+ Searcher.Page = 1;
+ }
+ if (Searcher.PageCount > 0 && Searcher.Page > Searcher.PageCount)
+ {
+ Searcher.Page = Searcher.PageCount;
+ }
+ EntityList = query.Skip((Searcher.Page - 1) * Searcher.Limit).Take(Searcher.Limit).AsNoTracking().ToList();
+ }
+ else //如果不需要分页则直接获取数据
+ {
+ EntityList = query.AsNoTracking().ToList();
+ Searcher.Count = EntityList.Count();
+ Searcher.Limit = EntityList.Count();
+ Searcher.PageCount = 1;
+ Searcher.Page = 1;
+ }
+ }
+ else
+ {
+ EntityList = query.AsNoTracking().ToList();
+ }
+ }
+ else
+ {
+ ProcessCommand(cmd);
+ }
+ IsSearched = true;
+ //调用AfterDoSearch函数来处理自定义的后续操作
+ AfterDoSearcher();
+ }
+
+
+ private void ProcessCommand(DbCommand cmd)
+ {
+ object total;
+
+ if (Searcher.Page <= 0)
+ {
+ Searcher.Page = 1;
+ }
+ if (DC.Database.IsMySql())
+ {
+ List parms = new List();
+ foreach (MySqlParameter item in cmd.Parameters)
+ {
+ parms.Add(new MySqlParameter(string.Format("@{0}", item.ParameterName), item.Value));
+ }
+ if (cmd.CommandType == CommandType.StoredProcedure)
+ {
+ parms.Add(new MySqlParameter("@SearchMode", Enum.GetName(typeof(ListVMSearchModeEnum), SearcherMode)));
+ parms.Add(new MySqlParameter("@NeedPage", (NeedPage && Searcher.Limit != -1)));
+ parms.Add(new MySqlParameter("@CurrentPage", Searcher.Page));
+ parms.Add(new MySqlParameter("@RecordsPerPage", Searcher.Limit));
+ parms.Add(new MySqlParameter("@Sort", Searcher.SortInfo?.Property));
+ parms.Add(new MySqlParameter("@SortDir", Searcher.SortInfo?.Direction));
+ parms.Add(new MySqlParameter("@IDs", Ids == null ? "" : Ids.ToSepratedString()));
+
+ MySqlParameter outp = new MySqlParameter("@TotalRecords", MySqlDbType.Int64)
+ {
+ Value = 0,
+ Direction = ParameterDirection.Output
+ };
+ parms.Add(outp);
+ }
+ var pa = parms.ToArray();
+
+ EntityList = DC.Run(cmd.CommandText, cmd.CommandType, pa).ToList();
+ if (cmd.CommandType == CommandType.StoredProcedure)
+ {
+ total = pa.Last().Value;
+ }
+ else
+ {
+ total = EntityList.Count;
+ }
+ }
+ else if (DC.Database.IsNpgsql())
+ {
+ List parms = new List();
+ foreach (NpgsqlParameter item in cmd.Parameters)
+ {
+ parms.Add(new NpgsqlParameter(string.Format("@{0}", item.ParameterName), item.Value));
+ }
+
+ if (cmd.CommandType == CommandType.StoredProcedure)
+ {
+ parms.Add(new NpgsqlParameter("@SearchMode", Enum.GetName(typeof(ListVMSearchModeEnum), SearcherMode)));
+ parms.Add(new NpgsqlParameter("@NeedPage", (NeedPage && Searcher.Limit != -1)));
+ parms.Add(new NpgsqlParameter("@CurrentPage", Searcher.Page));
+ parms.Add(new NpgsqlParameter("@RecordsPerPage", Searcher.Limit));
+ parms.Add(new NpgsqlParameter("@Sort", Searcher.SortInfo?.Property));
+ parms.Add(new NpgsqlParameter("@SortDir", Searcher.SortInfo?.Direction));
+ parms.Add(new NpgsqlParameter("@IDs", Ids == null ? "" : Ids.ToSepratedString()));
+
+ NpgsqlParameter outp = new NpgsqlParameter("@TotalRecords", NpgsqlDbType.Bigint)
+ {
+ Value = 0,
+ Direction = ParameterDirection.Output
+ };
+ parms.Add(outp);
+ }
+ var pa = parms.ToArray();
+
+ EntityList = DC.Run(cmd.CommandText, cmd.CommandType, pa).ToList();
+ if (cmd.CommandType == CommandType.StoredProcedure)
+ {
+ total = pa.Last().Value;
+ }
+ else
+ {
+ total = EntityList.Count;
+ }
+ }
+ else
+ {
+ List parms = new List();
+ foreach (SqlParameter item in cmd.Parameters)
+ {
+ parms.Add(new SqlParameter(string.Format("@{0}", item.ParameterName), item.Value));
+ }
+ if (cmd.CommandType == CommandType.StoredProcedure)
+ {
+
+ parms.Add(new SqlParameter("@SearchMode", Enum.GetName(typeof(ListVMSearchModeEnum), SearcherMode)));
+ parms.Add(new SqlParameter("@NeedPage", (NeedPage && Searcher.Limit != -1)));
+ parms.Add(new SqlParameter("@CurrentPage", Searcher.Page));
+ parms.Add(new SqlParameter("@RecordsPerPage", Searcher.Limit));
+ parms.Add(new SqlParameter("@Sort", Searcher.SortInfo?.Property));
+ parms.Add(new SqlParameter("@SortDir", Searcher.SortInfo?.Direction));
+ parms.Add(new SqlParameter("@IDs", Ids == null ? "" : Ids.ToSepratedString()));
+
+ SqlParameter outp = new SqlParameter("@TotalRecords", 0)
+ {
+ Direction = ParameterDirection.Output
+ };
+ parms.Add(outp);
+ }
+ var pa = parms.ToArray();
+
+ EntityList = DC.Run(cmd.CommandText, cmd.CommandType, pa).ToList();
+ if (cmd.CommandType == CommandType.StoredProcedure)
+ {
+ total = pa.Last().Value;
+ }
+ else
+ {
+ total = EntityList.Count;
+ }
+
+ }
+ if (NeedPage && Searcher.Limit != -1)
+ {
+ if (total != null)
+ {
+ try
+ {
+ Searcher.Count = long.Parse(total.ToString());
+ Searcher.PageCount = (int)((Searcher.Count - 1) / Searcher.Limit + 1);
+ }
+ catch { }
+ }
+ }
+ else
+ {
+ Searcher.PageCount = EntityList.Count;
+ }
+
+ }
+
+ public DateTime AddTime(DateTime dt, string type, int size)
+ {
+ switch (type)
+ {
+ case "year":
+ return dt.AddYears(size);
+ case "month":
+ return dt.AddMonths(size);
+ case "day":
+ return dt.AddDays(size);
+ case "hour":
+ return dt.AddHours(size);
+ case "minute":
+ return dt.AddMinutes(size);
+ case "second":
+ return dt.AddSeconds(size);
+ default:
+ return dt;
+ }
+ }
+
+ ///
+ /// 搜索后运行的函数,继承的类如果需要在搜索结束后进行其他操作,可重载这个函数
+ ///
+ public virtual void AfterDoSearcher()
+ {
+ if (SearcherMode == ListVMSearchModeEnum.Selector && Ids != null && Ids.Count > 0 && EntityList != null && EntityList.Count > 0)
+ {
+ foreach (var item in EntityList)
+ {
+ if (string.IsNullOrEmpty(SelectorValueField) || SelectorValueField.ToLower() == "id")
+ {
+ var id = item.GetID();
+ if (Ids.Contains(id.ToString()))
+ {
+ item.Checked = true;
+ }
+ }
+ else
+ {
+ var v = item.GetPropertyValue(SelectorValueField);
+ if (Ids.Contains(v.ToString()))
+ {
+ item.Checked = true;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// 删除所有ActionGridColumn的列
+ ///
+ public void RemoveActionColumn(object root = null)
+ {
+ if (root == null)
+ {
+ if (GridHeaders == null)
+ {
+ GetHeaders();
+ }
+ root = GridHeaders;
+ }
+ if (root != null)
+ {
+ //IEnumerable>
+ var aroot = root as List>;
+ var toRemove = aroot.Where(x => x.ColumnType == GridColumnTypeEnum.Action).FirstOrDefault();
+ aroot.Remove(toRemove);
+ foreach (var child in aroot)
+ {
+ if (child.Children != null && child.Children.Count() > 0)
+ {
+ RemoveActionColumn(child.Children);
+ }
+ }
+ }
+ }
+
+ public void RemoveAction()
+ {
+ _gridActions = new List();
+ }
+
+ public void RemoveActionAndIdColumn(IEnumerable> root = null)
+ {
+ if (root == null)
+ {
+ if (GridHeaders == null)
+ {
+ GetHeaders();
+ }
+ root = GridHeaders;
+ }
+ if (root != null)
+ {
+ var aroot = root as List>;
+ List> remove = null;
+ var idpro = typeof(TModel).GetSingleProperty("ID")?.PropertyType;
+ if (idpro == typeof(string))
+ {
+ remove = aroot.Where(x => x.ColumnType == GridColumnTypeEnum.Action || x.Hide == true || x.DisableExport).ToList();
+ }
+ else
+ {
+ remove = aroot.Where(x => x.ColumnType == GridColumnTypeEnum.Action || x.Hide == true || x.DisableExport || x.FieldName?.ToLower() == "id").ToList();
+ }
+ foreach (var item in remove)
+ {
+ aroot.Remove(item);
+ }
+ foreach (var child in root)
+ {
+ if (child.Children != null && child.Children.Count() > 0)
+ {
+ RemoveActionAndIdColumn(child.Children);
+ }
+ }
+ }
+ }
+
+
+ ///
+ /// 添加Error列,主要为批量模式使用
+ ///
+ public void AddErrorColumn()
+ {
+ GetHeaders();
+ //寻找所有Header为错误信息的列,如果没有则添加
+ if (GridHeaders.Where(x => x.Field == "BatchError").FirstOrDefault() == null)
+ {
+ var temp = GridHeaders as List>;
+ if (temp.Where(x => x.ColumnType == GridColumnTypeEnum.Action).FirstOrDefault() == null)
+ {
+ temp.Add(this.MakeGridColumn(x => x.BatchError, Width: 200, Header: Core.CoreProgram._localizer?["Sys.Error"]).SetForeGroundFunc(x => "ff0000").SetFixed(GridColumnFixedEnum.Right));
+ }
+ else
+ {
+ temp.Insert(temp.Count - 1, this.MakeGridColumn(x => x.BatchError, Width: 200, Header: Core.CoreProgram._localizer?["Sys.Error"]).SetForeGroundFunc(x => "ff0000").SetFixed(GridColumnFixedEnum.Right));
+ }
+ }
+ }
+
+ public void ProcessListError(List Entities)
+ {
+ if(Entities == null)
+ {
+ return;
+ }
+ EntityList = Entities;
+ IsSearched = true;
+ bool haserror = false;
+ List keys = new List();
+ if (string.IsNullOrEmpty(DetailGridPrix) == false)
+ {
+ if (EntityList.Any(x => x.BatchError != null))
+ {
+ haserror = true;
+ }
+ else
+ {
+ foreach (var item in MSD.Keys)
+ {
+ if (item.StartsWith(DetailGridPrix+"["))
+ {
+ var errors = MSD[item];
+ if (errors.Count > 0)
+ {
+ Regex r = new Regex($"{DetailGridPrix}\\[(.*?)\\]");
+ try
+ {
+ if (int.TryParse(r.Match(item).Groups[1].Value, out int index))
+ {
+ EntityList[index].BatchError = errors.Select(x => x.ErrorMessage).ToSepratedString();
+ keys.Add(item);
+ haserror = true;
+ }
+ }
+ catch { }
+ }
+ }
+ }
+ foreach (var item in keys)
+ {
+ MSD.RemoveModelError(item);
+ }
+ }
+ if (haserror)
+ {
+ AddErrorColumn();
+ }
+ }
+ }
+
+ public TModel CreateEmptyEntity()
+ {
+ return typeof(TModel).GetConstructor(Type.EmptyTypes).Invoke(null) as TModel;
+ }
+
+ public void ClearEntityList()
+ {
+ EntityList?.Clear();
+ }
+
+ public string DetailGridPrix { get; set; }
+
+ public Type ModelType => typeof(TModel);
+
+ #endregion
+
+ public virtual void UpdateEntityList(bool updateAllFields = false)
+ {
+ if (EntityList != null)
+ {
+ var ftype = EntityList.GetType().GenericTypeArguments.First();
+ var itemPros = ftype.GetAllProperties();
+
+ foreach (var newitem in EntityList)
+ {
+ var subtype = newitem.GetType();
+ if (typeof(IBasePoco).IsAssignableFrom( subtype))
+ {
+ IBasePoco ent = newitem as IBasePoco;
+ if (ent.UpdateTime == null)
+ {
+ ent.UpdateTime = DateTime.Now;
+ }
+ if (string.IsNullOrEmpty(ent.UpdateBy))
+ {
+ ent.UpdateBy = LoginUserInfo?.ITCode;
+ }
+ }
+ //循环页面传过来的子表数据,将关联到TopBasePoco的字段设为null,并且把外键字段的值设定为主表ID
+ foreach (var itempro in itemPros)
+ {
+ if (itempro.PropertyType.IsSubclassOf(typeof(TopBasePoco)))
+ {
+ itempro.SetValue(newitem, null);
+ }
+ }
+ }
+
+ IEnumerable data = null;
+ //打开新的数据库联接,获取数据库中的主表和子表数据
+ using (var ndc = DC.CreateNew())
+ {
+ var ids = EntityList.Select(x => x.GetID().ToString()).ToList();
+ data = ndc.Set().AsNoTracking().Where(ids.GetContainIdExpression()).ToList();
+ }
+ //比较子表原数据和新数据的区别
+ IEnumerable toadd = null;
+ IEnumerable toremove = null;
+ Utils.CheckDifference(data, EntityList, out toremove, out toadd);
+ //设定子表应该更新的字段
+ List setnames = new List();
+ foreach (var field in FC.Keys)
+ {
+ if (field.StartsWith("EntityList[0]."))
+ {
+ string name = field.Replace("EntityList[0].", "");
+ setnames.Add(name);
+ }
+ }
+
+ //前台传过来的数据
+ foreach (var newitem in EntityList)
+ {
+ //数据库中的数据
+ foreach (var item in data)
+ {
+ //需要更新的数据
+ if (newitem.GetID().ToString() == item.GetID().ToString())
+ {
+ dynamic i = newitem;
+ var newitemType = item.GetType();
+ foreach (var itempro in itemPros)
+ {
+ if (!itempro.PropertyType.IsSubclassOf(typeof(TopBasePoco)) && (updateAllFields == true || setnames.Contains(itempro.Name)))
+ {
+ var notmapped = itempro.GetCustomAttribute();
+ if (itempro.Name != "ID" && notmapped == null && itempro.PropertyType.IsList() == false)
+ {
+ DC.UpdateProperty(i, itempro.Name);
+ }
+ }
+ }
+ if ( typeof(IBasePoco).IsAssignableFrom( item.GetType()))
+ {
+ DC.UpdateProperty(i, "UpdateTime");
+ DC.UpdateProperty(i, "UpdateBy");
+ }
+ }
+ }
+ }
+ //需要删除的数据
+ foreach (var item in toremove)
+ {
+ //如果是PersistPoco,则把IsValid设为false,并不进行物理删除
+ if (typeof(IPersistPoco).IsAssignableFrom( ftype))
+ {
+ (item as IPersistPoco).IsValid = false;
+ if (typeof(IBasePoco).IsAssignableFrom(ftype))
+ {
+ (item as IBasePoco).UpdateTime = DateTime.Now;
+ (item as IBasePoco).UpdateBy = LoginUserInfo?.ITCode;
+ }
+ dynamic i = item;
+ DC.UpdateEntity(i);
+ }
+ else
+ {
+ foreach (var itempro in itemPros)
+ {
+ if (itempro.PropertyType.IsSubclassOf(typeof(TopBasePoco)))
+ {
+ itempro.SetValue(item, null);
+ }
+ }
+ dynamic i = item;
+ DC.DeleteEntity(i);
+ }
+ }
+ //需要添加的数据
+ foreach (var item in toadd)
+ {
+ if (typeof(IBasePoco).IsAssignableFrom( item.GetType()))
+ {
+ IBasePoco ent = item as IBasePoco;
+ if (ent.CreateTime == null)
+ {
+ ent.CreateTime = DateTime.Now;
+ }
+ if (string.IsNullOrEmpty(ent.CreateBy))
+ {
+ ent.CreateBy = LoginUserInfo?.ITCode;
+ }
+ }
+ DC.AddEntity(item);
+
+
+ }
+
+ DC.SaveChanges();
+ }
+ }
+ }
+}
diff --git a/WalkingTec.Mvvm/WalkingTec.Mvvm.Core/BaseSearcher.cs b/WalkingTec.Mvvm/WalkingTec.Mvvm.Core/BaseSearcher.cs
new file mode 100644
index 0000000..1ccc8ab
--- /dev/null
+++ b/WalkingTec.Mvvm/WalkingTec.Mvvm.Core/BaseSearcher.cs
@@ -0,0 +1,196 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+using WalkingTec.Mvvm.Core.Extensions;
+
+namespace WalkingTec.Mvvm.Core
+{
+ ///
+ /// 搜索条件基类,一般和ListVM配合使用实现对ListVM的搜索功能。
+ ///
+ public class BaseSearcher : ISearcher
+ {
+ #region Property
+
+ #region 分页相关
+ ///
+ /// 当前页
+ ///
+ public int Page { get; set; }
+ ///
+ /// 每页数
+ ///
+ public int Limit { get; set; }
+ ///
+ /// 记录数
+ ///
+ [JsonIgnore]
+ public long Count { get; set; }
+ ///
+ /// 分页数
+ ///
+ [JsonIgnore]
+ public int PageCount { get; set; }
+ #endregion
+
+ ///
+ /// 记录 Controller 中的表单数据
+ ///
+ [JsonIgnore]
+ public Dictionary FC { get; set; }
+
+ [JsonIgnore]
+ public IModelStateService MSD { get => Wtm?.MSD; }
+
+ ///
+ /// 获取VM的全名
+ ///
+ [JsonIgnore]
+ public string VMFullName
+ {
+ get
+ {
+ var name = GetType().AssemblyQualifiedName;
+ name = name.Substring(0, name.LastIndexOf(", Version="));
+ return name;
+ }
+ }
+
+ private IDataContext _dc;
+ ///
+ /// 数据库环境
+ ///
+ [JsonIgnore]
+ public IDataContext DC
+ {
+ get
+ {
+ if (_dc == null)
+ {
+ return Wtm?.DC;
+ }
+ else
+ {
+ return _dc;
+ }
+ }
+ set
+ {
+ _dc = value;
+ }
+ }
+
+ ///
+ /// Session信息
+ ///
+ [JsonIgnore]
+ public ISessionService Session { get => Wtm?.Session; }
+
+ ///
+ /// 当前登录人信息
+ ///
+ [JsonIgnore]
+ public LoginUserInfo LoginUserInfo { get => Wtm?.LoginUserInfo; }
+
+ [JsonIgnore]
+ public string ViewDivId { get; set; }
+ #region 未使用
+ ///
+ /// 排序信息
+ ///
+ public SortInfo SortInfo { get; set; }
+
+ ///
+ /// 前台搜索框是否展开
+ ///
+ [JsonIgnore]
+ public bool? IsExpanded { get; set; }
+
+ private Guid _uniqueId;
+ [JsonIgnore]
+ public string UniqueId
+ {
+ get
+ {
+ if (_uniqueId == Guid.Empty)
+ {
+ _uniqueId = Guid.NewGuid();
+ }
+ return _uniqueId.ToNoSplitString();
+ }
+ }
+
+ [JsonIgnore]
+ public WTMContext Wtm { get; set; }
+ #endregion
+
+ #endregion
+
+ #region Event
+
+ ///
+ /// InitVM 完成后触发的事件
+ ///
+ public event Action OnAfterInit;
+ ///
+ /// ReInitVM 完成后触发的事件
+ ///
+ public event Action OnAfterReInit;
+
+ #endregion
+
+ #region Method
+
+ ///
+ /// 调用 InitVM 并触发 OnAfterInit 事件
+ ///
+ public void DoInit()
+ {
+ InitVM();
+ OnAfterInit?.Invoke(this);
+ }
+
+ ///
+ /// 调用 ReInitVM 并触发 OnAfterReInit 事件
+ ///
+ public void DoReInit()
+ {
+ ReInitVM();
+ OnAfterReInit?.Invoke(this);
+ }
+
+ ///
+ /// 初始化ViewModel,框架会在创建VM实例之后自动调用本函数
+ ///
+ protected virtual void InitVM()
+ {
+ }
+
+ ///
+ /// 从新初始化ViewModel,框架会在验证失败时自动调用本函数
+ ///
+ protected virtual void ReInitVM()
+ {
+ InitVM();
+ }
+
+ public virtual void Validate()
+ {
+
+ }
+ ///
+ /// 将源 VM 的 FC 等内容复制到本VM中
+ ///
+ ///
+ public void CopyContext(IBaseVM vm)
+ {
+ FC = vm.FC;
+ this.Wtm = vm.Wtm;
+ this.ViewDivId = vm.ViewDivId;
+ }
+
+ #endregion
+
+ }
+}
diff --git a/WalkingTec.Mvvm/WalkingTec.Mvvm.Core/BaseTemplateVM.cs b/WalkingTec.Mvvm/WalkingTec.Mvvm.Core/BaseTemplateVM.cs
new file mode 100644
index 0000000..539ae64
--- /dev/null
+++ b/WalkingTec.Mvvm/WalkingTec.Mvvm.Core/BaseTemplateVM.cs
@@ -0,0 +1,345 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Data;
+using System.IO;
+using System.Linq;
+using NPOI.HSSF.UserModel;
+using NPOI.HSSF.Util;
+using NPOI.SS.UserModel;
+using NPOI.XSSF.UserModel;
+
+namespace WalkingTec.Mvvm.Core
+{
+ public class BaseTemplateVM : BaseVM
+ {
+ #region 属性
+ ///
+ /// 下载模板显示名称
+ ///
+ public string FileDisplayName { get; set; }
+
+ ///
+ /// 是否验证模板类型(当其他系统模板导入到某模块时可设置为False)
+ ///
+ public bool ValidityTemplateType { get; set; }
+
+ ///
+ /// 需要导出的数据
+ ///
+ public DataTable TemplateDataTable { get; set; }
+
+ ///
+ /// 下载模版页面参数
+ ///
+ public Dictionary Parms { get; set; }
+
+ ///
+ /// Excel索引
+ ///
+ public long ExcelIndex { get; set; }
+ #endregion
+
+ #region 构造函数
+ public BaseTemplateVM()
+ {
+ ValidityTemplateType = true;
+ Parms = new Dictionary();
+ var propetys = this.GetType().GetFields().Where(x => x.FieldType == typeof(ExcelPropety)).ToList();
+ for (int porpetyIndex = 0; porpetyIndex < propetys.Count(); porpetyIndex++)
+ {
+ ExcelPropety excelPropety = (ExcelPropety)propetys[porpetyIndex].GetValue(this);
+ if (propetys[porpetyIndex].GetCustomAttributes(typeof(DisplayAttribute), false).Length == 0)
+ {
+ excelPropety.ColumnName = excelPropety.FieldDisplayName;
+ }
+ else
+ {
+ excelPropety.ColumnName = propetys[porpetyIndex].GetPropertyDisplayName();
+ }
+ }
+ }
+ #endregion
+
+ #region 初始化Excel属性数据
+ ///
+ /// 初始化Excel属性数据 包括动态列,列表中的下拉选项
+ ///
+ public virtual void InitExcelData()
+ {
+
+ }
+
+ public virtual void InitCustomFormat()
+ {
+
+ }
+
+ #endregion
+
+ #region 初始化模版数据
+ ///
+ /// 初始化模版数据
+ ///
+ public virtual void SetTemplateDataValus()
+ {
+
+ }
+ #endregion
+
+ #region 生成模板
+ ///
+ /// 生成模板
+ ///
+ /// 文件名
+ /// 生成的模版文件
+ public byte[] GenerateTemplate(out string displayName)
+ {
+ //设置导出的文件名称
+ string SheetName = !string.IsNullOrEmpty(FileDisplayName) ? FileDisplayName : this.GetType().Name;
+ displayName = SheetName + "_" + DateTime.Now.ToString("yyyy-MM-dd") + "_" + DateTime.Now.ToString("hh^mm^ss") + ".xlsx";
+
+ //1.声明Excel文档
+ IWorkbook workbook = new XSSFWorkbook();
+
+ //加载初始化数据和下拉菜单数据,可重载
+ InitExcelData();
+
+ //设置TemplateDataTable的各列的类型
+ CreateDataTable();
+
+ //设置初始化数据到DataTable中
+ SetTemplateDataValus();
+
+ //2.设置workbook的sheet页
+ ISheet sheet = workbook.CreateSheet();
+ workbook.SetSheetName(0, SheetName);
+
+ //3.设置Sheet页的Row
+ IRow row = sheet.CreateRow(0);
+ row.HeightInPoints = 20;
+
+ ISheet enumSheet = workbook.CreateSheet();
+ IRow enumSheetRow1 = enumSheet.CreateRow(0);
+ enumSheetRow1.CreateCell(0).SetCellValue(CoreProgram._localizer?["Sys.Yes"]);
+ enumSheetRow1.CreateCell(1).SetCellValue(CoreProgram._localizer?["Sys.No"]);
+ enumSheetRow1.CreateCell(2).SetCellValue(this.GetType().Name); //为模板添加标记,必要时可添加版本号
+
+ ISheet dataSheet = workbook.CreateSheet();
+
+ #region 设置excel模板列头
+ //默认灰色
+ var headerStyle = GetCellStyle(workbook);
+ headerStyle.IsLocked = true;
+
+ //黄色
+ var yellowStyle = GetCellStyle(workbook, BackgroudColorEnum.Yellow);
+ yellowStyle.IsLocked = true;
+
+ //红色
+ var redStyle = GetCellStyle(workbook, BackgroudColorEnum.Red);
+ redStyle.IsLocked = true;
+
+ //取得所有ExcelPropety
+ var propetys = this.GetType().GetFields().Where(x => x.FieldType == typeof(ExcelPropety)).ToList();
+
+ //设置列的索引
+ int _currentColunmIndex = 0;
+
+ //设置Excel是否需要保护,默认不保护
+ bool IsProtect = false;
+
+ //循环类的属性,赋值给列
+ for (int porpetyIndex = 0; porpetyIndex < propetys.Count(); porpetyIndex++)
+ {
+ //依次获取属性字段
+ ExcelPropety excelPropety = (ExcelPropety)propetys[porpetyIndex].GetValue(this);
+ ColumnDataType dateType = (excelPropety.DataType == ColumnDataType.DateTime || excelPropety.DataType == ColumnDataType.Date) ? ColumnDataType.Text : excelPropety.DataType; //日期类型默认设置成Text类型,在赋值时会进行日期验证
+
+ //设置是否保护Excel
+ if (excelPropety.ReadOnly)
+ {
+ IsProtect = true;
+ }
+ //给必填项加星号
+ string colName = excelPropety.IsNullAble ? excelPropety.ColumnName : excelPropety.ColumnName + "*";
+ row.CreateCell(_currentColunmIndex).SetCellValue(colName);
+
+ //修改列头样式
+ switch (excelPropety.BackgroudColor)
+ {
+ case BackgroudColorEnum.Yellow:
+ row.Cells[_currentColunmIndex].CellStyle = yellowStyle;
+ break;
+ case BackgroudColorEnum.Red:
+ row.Cells[_currentColunmIndex].CellStyle = redStyle;
+ break;
+ default:
+ row.Cells[_currentColunmIndex].CellStyle = headerStyle;
+ break;
+ }
+
+ var dataStyle = workbook.CreateCellStyle();
+ var dataFormat = workbook.CreateDataFormat();
+
+ if (dateType == ColumnDataType.Dynamic)
+ {
+ int dynamicColCount = excelPropety.DynamicColumns.Count();
+ for (int dynamicColIndex = 0; dynamicColIndex < dynamicColCount; dynamicColIndex++)
+ {
+ var dynamicCol = excelPropety.DynamicColumns.ToList()[dynamicColIndex];
+ string dynamicColName = excelPropety.IsNullAble ? dynamicCol.ColumnName : dynamicCol.ColumnName + "*";
+ row.CreateCell(_currentColunmIndex).SetCellValue(dynamicColName);
+ row.Cells[_currentColunmIndex].CellStyle = headerStyle;
+ if (dynamicCol.ReadOnly)
+ {
+ IsProtect = true;
+ }
+ //设定列宽
+ if (excelPropety.CharCount > 0)
+ {
+ sheet.SetColumnWidth(_currentColunmIndex, excelPropety.CharCount * 256);
+ dataStyle.WrapText = true;
+ }
+ else
+ {
+ sheet.AutoSizeColumn(_currentColunmIndex);
+ }
+ //设置单元格样式及数据类型
+ dataStyle.IsLocked = excelPropety.ReadOnly;
+ dynamicCol.SetColumnFormat(dynamicCol.DataType, _currentColunmIndex, sheet, dataSheet, dataStyle, dataFormat);
+ _currentColunmIndex++;
+ }
+ }
+ else
+ {
+ //设定列宽
+ if (excelPropety.CharCount > 0)
+ {
+ sheet.SetColumnWidth(_currentColunmIndex, excelPropety.CharCount * 256);
+ dataStyle.WrapText = true;
+ }
+ else
+ {
+ sheet.AutoSizeColumn(_currentColunmIndex);
+ }
+ //设置是否锁定
+ dataStyle.IsLocked = excelPropety.ReadOnly;
+ //设置单元格样式及数据类型
+ excelPropety.SetColumnFormat(dateType, _currentColunmIndex, sheet, dataSheet, dataStyle, dataFormat);
+ _currentColunmIndex++;
+ }
+ }
+ #endregion
+
+ #region 添加模版数据
+ if (TemplateDataTable.Rows.Count > 0)
+ {
+ for (int i = 0; i < TemplateDataTable.Rows.Count; i++)
+ {
+ DataRow tableRow = TemplateDataTable.Rows[i];
+ IRow dataRow = sheet.CreateRow(1 + i);
+ for (int porpetyIndex = 0; porpetyIndex < propetys.Count(); porpetyIndex++)
+ {
+ string colName = propetys[porpetyIndex].Name;
+ tableRow[colName].ToString();
+ dataRow.CreateCell(porpetyIndex).SetCellValue(tableRow[colName].ToString());
+ }
+ }
+ }
+ #endregion
+
+ //冻结行
+ sheet.CreateFreezePane(0, 1, 0, 1);
+
+ //锁定excel
+ if (IsProtect)
+ {
+ sheet.ProtectSheet("password");
+ }
+
+ //隐藏前2个Sheet
+ workbook.SetSheetHidden(1, SheetState.Hidden);
+ workbook.SetSheetHidden(2, SheetState.Hidden);
+
+ //返回byte数组
+ MemoryStream ms = new MemoryStream();
+ workbook.Write(ms);
+ return ms.ToArray();
+ }
+ #endregion
+
+ #region 取得表头的样式
+ private static ICellStyle GetCellStyle(IWorkbook workbook, BackgroudColorEnum backgroudColor = BackgroudColorEnum.Grey)
+ {
+ var headerStyle = workbook.CreateCellStyle();
+
+ //设定表头样式
+ headerStyle.BorderBottom = BorderStyle.Thin;
+ headerStyle.BorderLeft = BorderStyle.Thin;
+ headerStyle.BorderRight = BorderStyle.Thin;
+ headerStyle.BorderTop = BorderStyle.Thin;
+
+ //用灰色填充背景
+ short headerbg;
+
+ switch (backgroudColor)
+ {
+ case BackgroudColorEnum.Grey:
+ headerbg = HSSFColor.LightBlue.Index;
+ break;
+ case BackgroudColorEnum.Yellow:
+ headerbg = HSSFColor.LightYellow.Index;
+ break;
+ case BackgroudColorEnum.Red:
+ headerbg = HSSFColor.Pink.Index;
+ break;
+ default:
+ headerbg = HSSFColor.Pink.Index;
+ break;
+ }
+
+ headerStyle.FillForegroundColor = headerbg;
+ headerStyle.FillPattern = FillPattern.SolidForeground;
+ headerStyle.FillBackgroundColor = headerbg;
+ headerStyle.Alignment = HorizontalAlignment.Center;
+ return headerStyle;
+ }
+ #endregion
+
+ #region 初始化DataTable(不含动态列)
+ private void CreateDataTable()
+ {
+ TemplateDataTable = new DataTable();
+ var propetys = this.GetType().GetFields().Where(x => x.FieldType == typeof(ExcelPropety)).ToList();
+ foreach (var p in propetys)
+ {
+ ExcelPropety excelPropety = (ExcelPropety)p.GetValue(this);
+ ColumnDataType dateType = excelPropety.DataType;
+ switch (dateType)
+ {
+ case ColumnDataType.Bool:
+ TemplateDataTable.Columns.Add(p.Name, typeof(bool));
+ break;
+ case ColumnDataType.Date:
+ TemplateDataTable.Columns.Add(p.Name, typeof(string));
+ break;
+ case ColumnDataType.Number:
+ TemplateDataTable.Columns.Add(p.Name, typeof(int));
+ break;
+ case ColumnDataType.Text:
+ TemplateDataTable.Columns.Add(p.Name, typeof(string));
+ break;
+ case ColumnDataType.Float:
+ TemplateDataTable.Columns.Add(p.Name, typeof(decimal));
+ break;
+ default:
+ TemplateDataTable.Columns.Add(p.Name, typeof(string));
+ break;
+ }
+ }
+ }
+ #endregion
+
+ }
+}
diff --git a/WalkingTec.Mvvm/WalkingTec.Mvvm.Core/BaseVM.cs b/WalkingTec.Mvvm/WalkingTec.Mvvm.Core/BaseVM.cs
new file mode 100644
index 0000000..fc6635b
--- /dev/null
+++ b/WalkingTec.Mvvm/WalkingTec.Mvvm.Core/BaseVM.cs
@@ -0,0 +1,284 @@
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Reflection;
+using System.Text.Json.Serialization;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Logging;
+
+using WalkingTec.Mvvm.Core.Extensions;
+using WalkingTec.Mvvm.Core.Support.Json;
+
+namespace WalkingTec.Mvvm.Core
+{
+
+ ///
+ /// 所有ViewModel的基类,提供了基本的功能
+ ///
+ public class BaseVM : IBaseVM
+ {
+ ///
+ /// BaseVM
+ ///
+ public BaseVM()
+ {
+ FC = new Dictionary();
+ }
+
+ #region Property
+
+ [JsonIgnore]
+ public WTMContext Wtm { get; set; }
+
+ private Guid _uniqueId;
+ ///
+ /// VM实例的Id
+ ///
+ [JsonIgnore]
+ public string UniqueId
+ {
+ get
+ {
+ if (_uniqueId == Guid.Empty)
+ {
+ _uniqueId = Guid.NewGuid();
+ }
+ return _uniqueId.ToNoSplitString();
+ }
+ }
+
+
+ ///
+ /// 前台传递过来的弹出窗口ID,多层弹出窗口用逗号分隔
+ ///
+ [JsonIgnore]
+ public string WindowIds { get => Wtm?.WindowIds; }
+
+ private string _viewdivid;
+ ///
+ /// PartialView中主Div的Id
+ ///
+ [JsonIgnore]
+ public string ViewDivId
+ {
+ set { _viewdivid = value; }
+ get
+ {
+ if (string.IsNullOrEmpty(_viewdivid))
+ {
+ _viewdivid = "ViewDiv" + UniqueId;
+ }
+ return _viewdivid;
+ }
+ }
+
+
+ private IDataContext _dc;
+ ///
+ /// 数据库环境
+ ///
+ [JsonIgnore]
+ public IDataContext DC
+ {
+ get
+ {
+ if (_dc == null)
+ {
+ return Wtm?.DC;
+ }
+ else
+ {
+ return _dc;
+ }
+ }
+ set
+ {
+ _dc = value;
+ }
+ }
+
+ ///
+ /// 获取VM的全名
+ ///
+ [JsonIgnore]
+ public string VMFullName
+ {
+ get
+ {
+ var name = GetType().AssemblyQualifiedName;
+ name = name.Substring(0, name.LastIndexOf(", Version="));
+ return name;
+ }
+ }
+
+ ///
+ /// 获取VM所在Dll
+ ///
+ [JsonIgnore]
+ public string CreatorAssembly
+ {
+ get; set;
+ }
+
+ ///
+ /// 获取当前使用的连接字符串
+ ///
+ [JsonIgnore]
+ public string CurrentCS { get => Wtm?.CurrentCS; }
+
+ ///
+ /// 记录Controller中传递过来的表单数据
+ ///
+ [JsonIgnore]
+ public Dictionary FC { get; set; }
+
+ ///
+ /// 获取配置文件的信息
+ ///
+ [JsonIgnore]
+ public Configs ConfigInfo { get=> Wtm?.ConfigInfo; }
+
+
+ [JsonIgnore]
+ public IUIService UIService { get=> Wtm?.UIService; }
+
+ ///
+ /// 当前弹出层ID
+ ///
+ [JsonIgnore]
+ public string CurrentWindowId { get => Wtm?.CurrentWindowId; }
+
+ ///
+ /// 父级弹出层ID
+ ///
+ [JsonIgnore]
+ public string ParentWindowId { get => Wtm?.ParentWindowId; }
+
+ [JsonIgnore]
+ public IDistributedCache Cache { get => Wtm?.Cache; }
+
+ ///
+ /// 当前登录人信息
+ ///
+ [JsonIgnore]
+ public LoginUserInfo LoginUserInfo { get=> Wtm?.LoginUserInfo;}
+
+ ///
+ /// 当前Url
+ ///
+ [JsonIgnore]
+ public string CurrentUrl { get => Wtm?.BaseUrl; }
+
+ ///
+ /// 记录原始提交页面
+ ///
+ [JsonIgnore]
+ public string FromView { get; set; }
+
+ ///
+ /// 记录当前页面
+ ///
+ [JsonIgnore]
+ public string CurrentView { get; set; }
+
+ ///
+ /// Session信息
+ ///
+ [JsonIgnore]
+ public ISessionService Session { get => Wtm?.Session; }
+
+ ///
+ /// Controller传递过来的ModelState信息
+ ///
+ [JsonIgnore]
+ public IModelStateService MSD { get => Wtm?.MSD; }
+
+ ///
+ /// 用于保存删除的附件ID
+ ///
+ public List DeletedFileIds { get; set; } = new List();
+
+ [JsonIgnore]
+ public string ControllerName { get; set; }
+
+ [JsonIgnore]
+ public IStringLocalizer Localizer { get => Wtm?.Localizer; }
+ #endregion
+
+ #region Event
+
+ ///