diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b0971a --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +bin/ +obj/ +*.user +/Publish +/packages +.vs +Logs/ +Upload +dbsettings.Development.json diff --git a/.gitmodules b/.gitmodules index 7f59953..73fc9bf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "framework"] - path = framework - url = http://118.178.224.202:3000/ewide/ewide_core +[submodule "Framework/EwideCode"] + path = EwideCore + url = http://118.178.224.202:3000/ewide/ewide_core.git diff --git a/Api/Ewide.Application/Entity/BsHouseCode.cs b/Api/Ewide.Application/Entity/BsHouseCode.cs new file mode 100644 index 0000000..ded5654 --- /dev/null +++ b/Api/Ewide.Application/Entity/BsHouseCode.cs @@ -0,0 +1,52 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ewide.Application +{ + [Table("bs_house_code")] + [Comment("房屋编码表")] + public class BsHouseCode : Core.DEntityBase + { + [Comment("系统中唯一的房屋编码,生成即不再变更")] + [MaxLength(50)] + [Required] + public string HouseCode { get; set; } + + [Comment("编号")] + [MaxLength(3)] + [Required] + public int No { get; set; } + + [Comment("项目ID")] + [MaxLength(36)] + [Required] + public string ProjectId { get; set; } + + [Comment("片区ID")] + [MaxLength(36)] + [Required] + public string ZoneId { get; set; } + + [Comment("详细地址")] + [MaxLength(500)] + [Required] + public string Address { get; set; } + + [Comment("性质")] + [Required] + public int Type { get; set; } + + [Comment("所属行业")] + [Required] + public int Industry { get; set; } + + [Comment("坐标-经度")] + [MaxLength(50)] + public string Lng { get; set; } + + [Comment("坐标-纬度")] + [MaxLength(50)] + public string Lat { get; set; } + } +} diff --git a/Api/Ewide.Application/Entity/BsHouseCompany.cs b/Api/Ewide.Application/Entity/BsHouseCompany.cs new file mode 100644 index 0000000..0849cdc --- /dev/null +++ b/Api/Ewide.Application/Entity/BsHouseCompany.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + [Table("bs_house_company")] + [Comment("房屋相关单位表")] + public class BsHouseCompany : Core.DEntityBase + { + /// + /// 单位类型,多选 + /// + [Comment("单位类型,多选")] + [MaxLength(20)] + public string Type { get; set; } + + /// + /// 单位名称 + /// + [Comment("单位名称")] + [MaxLength(200)] + public string Name { get; set; } + + /// + /// 信息 + /// + [Comment("信息")] + public string Info { get; set; } + } +} diff --git a/Api/Ewide.Application/Entity/BsHouseInfo.cs b/Api/Ewide.Application/Entity/BsHouseInfo.cs new file mode 100644 index 0000000..f7c8ea8 --- /dev/null +++ b/Api/Ewide.Application/Entity/BsHouseInfo.cs @@ -0,0 +1,378 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + [Table("bs_house_info")] + [Comment("房屋信息表")] + public class BsHouseInfo : Core.DEntityBase + { + /// + /// HouseCode主键ID + /// + [Comment("bs_house_code主键Id")] + [MaxLength(36)] + [Required] + public string HouseCodeId { get; set; } + + /// + /// 幢名称 + /// + [Comment("幢名称")] + [MaxLength(255)] + public string BuildingName { get; set; } + + /// + /// 土地性质 + /// + [Comment("土地性质")] + public int? LandAttribute { get; set; } + + /// + /// 结构类型 + /// + [Comment("结构类型")] + public int? StructureType { get; set; } + + /// + /// 基础情况 + /// + [Comment("基础情况")] + public int? BaseInfo { get; set; } + + /// + /// 抗震等级 + /// + [Comment("抗震等级")] + public int? SeismicGrade { get; set; } + + /// + /// 竣工日期 + /// + [Comment("竣工日期")] + public DateTime? CompletedDate { get; set; } + + /// + /// 设计使用年限 + /// + [Comment("设计使用年限")] + public int? UsefulYear { get; set; } + + /// + /// 总层数 + /// + [Comment("总层数")] + public int? TotalFloor { get; set; } + + /// + /// 地上层数 + /// + [Comment("地上层数")] + public int? LandFloorCount { get; set; } + + /// + /// 地上商业层数开始 + /// + [Comment("地上商业层数开始")] + public int? LandBsFloorStart { get; set; } + + /// + /// 地上商业层数结束 + /// + [Comment("地上商业层数结束")] + public int? LandBsFloorEnd { get; set; } + + /// + /// 地上车棚层开始 + /// + [Comment("地上车棚层开始")] + public int? LandBikeFloorStart { get; set; } + + /// + /// 地上住宅层开始 + /// + [Comment("地上住宅层开始")] + public int? LandResidenceFloorStart { get; set; } + + /// + /// 地上住宅层结束 + /// + [Comment("地上住宅层结束")] + public int? LandResidenceFloorEnd { get; set; } + + /// + /// 地下层数 + /// + [Comment("地下层数")] + public int? UnderFloorCount { get; set; } + + /// + /// 总建筑面积 + /// + [Comment("总建筑面积")] + public float? TotalArea { get; set; } + + /// + /// 有无建筑幕墙 + /// + [Comment("有无建筑幕墙")] + public int? CurtainWall { get; set; } + + /// + /// 有无面砖 + /// + [Comment("有无面砖")] + public int? FaceBrick { get; set; } + + /// + /// 有无涂料 + /// + [Comment("有无涂料")] + public int? Coating { get; set; } + + /// + /// 有无粉刷 + /// + [Comment("有无粉刷")] + public int? Painting { get; set; } + + /// + /// 电梯 + /// + [Comment("电梯")] + [MaxLength(20)] + public string Elevator { get; set; } + + /// + /// 总户数(户) + /// + [Comment("总户数(户)")] + public int? HouseHolds { get; set; } + + /// + /// 房屋单元数 + /// + [Comment("房屋单元数")] + public int? Units { get; set; } + + /// + /// 各单元每层户数(户) + /// + [Comment("各单元每层户数(户)")] + public int? UnitFloorHolds { get; set; } + + /// + /// 建设单位ID + /// + [Comment("建设单位ID")] + [MaxLength(36)] + public string BuildingUnitId { get; set; } + + /// + /// 设计单位ID + /// + [Comment("设计单位ID")] + [MaxLength(36)] + public string DesingerUnitId { get; set; } + + /// + /// 施工单位ID + /// + [Comment("施工单位ID")] + [MaxLength(36)] + public string ConstructionUnitId { get; set; } + + /// + /// 监理单位ID + /// + [Comment("监理单位ID")] + [MaxLength(36)] + public string MonitorUnitId { get; set; } + + /// + /// 产权性质 + /// + [Comment("产权性质")] + [MaxLength(100)] + public string PropertyRights { get; set; } + + /// + /// 总共套数 + /// + [Comment("总共套数")] + public int? HouseCount { get; set; } + + /// + /// 直管公房套数 + /// + [Comment("直管公房套数")] + public int? StraightHouseCount { get; set; } + + /// + /// 自管公房套数 + /// + [Comment("自管公房套数")] + public int? SelfHouseCount { get; set; } + + /// + /// 私房套数 + /// + [Comment("私房套数")] + public int? PrivateHouseCount { get; set; } + + /// + /// 商品房套数 + /// + [Comment("商品房套数")] + public int? BusinessCount { get; set; } + + /// + /// 房改房套数 + /// + [Comment("房改房套数")] + public int? ChangeHouseCount { get; set; } + + /// + /// 拆迁安置房套数 + /// + [Comment("拆迁安置房套数")] + public int? ResettlementHouseCount { get; set; } + + /// + /// 其它套数 + /// + [Comment("其它套数")] + public int? OtherCount { get; set; } + + /// + /// 产权单位ID + /// + [Comment("产权单位ID")] + [MaxLength(36)] + public string PropertyUnitId { get; set; } + + /// + /// 物业单位ID + /// + [Comment("物业单位ID")] + [MaxLength(36)] + public string WuYeUnitId { get; set; } + + /// + /// 图纸资料存档处 + /// + [Comment("图纸资料存档处")] + [MaxLength(50)] + public string DrawingMaterial { get; set; } + + /// + /// 其他图纸存档 + /// + [Comment("其他图纸存档")] + [MaxLength(100)] + public string DrawingMaterialText { get; set; } + + /// + /// 立项文件 + /// + [Comment("立项文件")] + [MaxLength(2000)] + public string AnEntryDocument { get; set; } + + /// + /// 规划许可 + /// + [Comment("规划许可")] + [MaxLength(2000)] + public string PlanningPermission { get; set; } + + /// + /// 竣工验收备案 + /// + [Comment("竣工验收备案")] + [MaxLength(2000)] + public string CompletionRecord { get; set; } + + /// + /// 监理资料 + /// + [Comment("监理资料")] + [MaxLength(2000)] + public string MonitorDocument { get; set; } + + /// + /// 鉴定报告 + /// + [Comment("鉴定报告")] + [MaxLength(2000)] + public string IdentificationReport { get; set; } + + /// + /// 其它资料 + /// + [Comment("其它资料")] + [MaxLength(2000)] + public string OtherDocument { get; set; } + + /// + /// 外立面照片 + /// + [Comment("外立面照片")] + [MaxLength(2000)] + public string FacadePhoto { get; set; } + + /// + /// 房屋等级 1,2,3,4,C,d + /// + [Comment("房屋等级 1,2,3,4,C,d")] + public int? HouseGrade { get; set; } + + /// + /// 房屋使用状态 + /// + [Comment("房屋使用状态")] + public int? HouseUsedStatus { get; set; } + + /// + /// -1 退回(审核被退回的数据) 1、待建档(需要建档的数据未暂存未保存的)2、暂存(点击了暂存的数据)3、待提交(点击了保存的数据) 5、审核中6、审核通过 + /// + [Comment("-1 退回(审核被退回的数据) 1、待建档(需要建档的数据未暂存未保存的)2、暂存(点击了暂存的数据)3、待提交(点击了保存的数据) 5、审核中6、审核通过")] + [Required] + public int State { get; set; } + + /// + /// 调查登记机构 + /// + [Comment("调查登记机构")] + [MaxLength(100)] + public string InvestigateAgency { get; set; } + + /// + /// 纸质资料调查人 + /// + [Comment("纸质资料调查人")] + [MaxLength(50)] + public string InvestigateUser { get; set; } + + /// + /// 纸质资料审核人 + /// + [Comment("纸质资料审核人")] + [MaxLength(50)] + public string OfflineAuditor { get; set; } + + /// + /// 主管部门 + /// + [Comment("主管部门")] + [MaxLength(50)] + public string CompetentDepartment { get; set; } + + } +} diff --git a/Api/Ewide.Application/Entity/BsHouseLog.cs b/Api/Ewide.Application/Entity/BsHouseLog.cs new file mode 100644 index 0000000..bdcd8bd --- /dev/null +++ b/Api/Ewide.Application/Entity/BsHouseLog.cs @@ -0,0 +1,46 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + [Table("bs_house_log")] + [Comment("房屋流转日志")] + public class BsHouseLog : Core.DEntityBase + { + [Comment("房屋编码Id")] + [MaxLength(36)] + public string HouseCodeId { get; set; } + + [Comment("目标处理人Id,可多个")] + public string TargetUserIds { get; set; } + + [Comment("步骤类型")] + public HouseLogType Type { get; set; } + + [Comment("状态")] + public HouseLogStatus Status { get; set; } + + [Comment("备注信息")] + public string Remark { get; set; } + + [Comment("完成时间")] + public DateTime? FinishedTime { get; set; } + + [Comment("完成者Id")] + [MaxLength(36)] + public string FinishedUserId { get; set; } + + [Comment("完成者名称")] + [MaxLength(20)] + public string FinishedUserName { get; set; } + + [Comment("排序")] + public int Sort { get; set; } + } +} diff --git a/Api/Ewide.Application/Entity/BsHouseMemberRelation.cs b/Api/Ewide.Application/Entity/BsHouseMemberRelation.cs new file mode 100644 index 0000000..98e42e2 --- /dev/null +++ b/Api/Ewide.Application/Entity/BsHouseMemberRelation.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + [Table("bs_house_member_relation")] + [Comment("房屋与人员关联表")] + public class BsHouseMemberRelation : Core.DEntityBase + { + [Comment("sys_user主键Id")] + [MaxLength(36)] + [Required] + public string SysUserId { get; set; } + + [Comment("bs_house_code主键Id")] + [MaxLength(36)] + [Required] + public string HouseCodeId { get; set; } + } +} diff --git a/Api/Ewide.Application/Entity/BsHouseProjectInfo.cs b/Api/Ewide.Application/Entity/BsHouseProjectInfo.cs new file mode 100644 index 0000000..1f294e2 --- /dev/null +++ b/Api/Ewide.Application/Entity/BsHouseProjectInfo.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Entity +{ + [Table("bs_house_projectinfo")] + [Comment("项目表")] + public class BsHouseProjectInfo : Core.DEntityBase + { + [Comment("名称")] + [MaxLength(100)] + [Required] + public string Name { get; set; } + + [Comment("备注")] + [MaxLength(1000)] + public string Note { get; set; } + + [Comment("排序")] + [Required] + public int Sort { get; set; } + + [Comment("区域ID")] + [MaxLength(36)] + [Required] + public string AreaCode { get; set; } + + [Comment("类型")] + [Required] + public int Type { get; set; } + } +} diff --git a/Api/Ewide.Application/Entity/BsHouseTask.cs b/Api/Ewide.Application/Entity/BsHouseTask.cs new file mode 100644 index 0000000..fde1073 --- /dev/null +++ b/Api/Ewide.Application/Entity/BsHouseTask.cs @@ -0,0 +1,266 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ewide.Application +{ + [Table("bs_house_task")] + [Comment("房屋巡查任务表")] + public class BsHouseTask : Core.DEntityBase + { + /// + /// bs_house_code主键Id + /// + [Comment("bs_house_code主键Id")] + [MaxLength(36)] + [Required] + public string HouseCodeId { get; set; } + + /// + /// 巡查日期 + /// + [Comment("巡查日期")] + public DateTime? PatrolDate { get; set; } + + /// + /// 巡查人姓名 + /// + [Comment("巡查人姓名")] + [MaxLength(50)] + public string PatrolUser { get; set; } + + /// + /// 沉降倾斜 + /// + [Comment("沉降倾斜")] + [MaxLength(1000)] + public string SettlementTilt { get; set; } + + /// + /// 沉降倾斜附件 + /// + [Comment("沉降倾斜附件")] + [MaxLength(2000)] + public string SettlementTiltFiles { get; set; } + + /// + /// 其他情况 + /// + [Comment("其他情况")] + [MaxLength(1000)] + public string OtherInfo { get; set; } + + /// + /// 其他情况附件 + /// + [Comment("其他情况附件")] + [MaxLength(2000)] + public string OtherInfoFiles { get; set; } + + /// + /// 初始等级 + /// + [Comment("初始等级")] + public int? InitGrade { get; set; } + + /// + /// 损坏等级 + /// + [Comment("损坏等级")] + public int? DamageGrade { get; set; } + + /// + /// 综合等级 + /// + [Comment("综合等级")] + public int? ComprehensiveGrade { get; set; } + + /// + /// 房屋场地 + /// + [Comment("房屋场地")] + [MaxLength(100)] + public string HouseSite { get; set; } + + /// + /// 相邻施工 + /// + [Comment("相邻施工")] + [MaxLength(100)] + public string AdjacentConstruction { get; set; } + + /// + /// 化学侵蚀 + /// + [Comment("化学侵蚀")] + [MaxLength(100)] + public string ChemicalErosion { get; set; } + + /// + /// 结构拆改 + /// + [Comment("结构拆改")] + public int? StructuralDismantling { get; set; } + + /// + /// 加层改造 + /// + [Comment("加层改造")] + public int? AddingLayer { get; set; } + + /// + /// 修缮加固 + /// + [Comment("修缮加固")] + [MaxLength(100)] + public string RepairAndReinforce { get; set; } + + /// + /// 历史灾害 + /// + [Comment("历史灾害")] + [MaxLength(100)] + public string HistoricalCalamity { get; set; } + + /// + /// 使用功能变更 + /// + [Comment("使用功能变更")] + [MaxLength(100)] + public string FunctionalChange { get; set; } + + /// + /// 其他调查内容 + /// + [Comment("其他调查内容")] + [MaxLength(1000)] + public string OtherContents { get; set; } + + /// + /// 主要安全隐患综述 + /// + [Comment("主要安全隐患综述")] + [MaxLength(1000)] + public string MainSafety { get; set; } + + /// + /// 处理意见 + /// + [Comment("处理意见")] + public int? HandlingOpinion { get; set; } + + /// + /// 处理意见备注 + /// + [Comment("处理意见备注")] + [MaxLength(1000)] + public string HandlingOpinionRemark { get; set; } + + /// + /// 整改情况 + /// + [Comment("整改情况")] + public int? RectifyAndReform { get; set; } + + /// + /// 整改情况备注 + /// + [Comment("整改情况备注")] + [MaxLength(1000)] + public string RectifyAndReformRemark { get; set; } + + /// + /// 巡查结果:1正常,-1异常 + /// + [Comment("巡查结果:1正常,-1异常")] + public int? PatrolResult { get; set; } + + /// + /// 巡查异常描述 + /// + [Comment("巡查异常描述")] + [MaxLength(1000)] + public string PatrolResultRemark { get; set; } + + /// + /// 任务人员ID + /// + [Comment("任务人员ID")] + [MaxLength(36)] + public string UserID { get; set; } + + /// + /// 任务截止时间 + /// + [Comment("任务截止时间")] + public DateTime? EndTime { get; set; } + + /// + /// 是否过期 + /// + [Comment("是否过期")] + public bool? IsDelay { get; set; } + + /// + /// -1:退回0:待处理1:保存待提交2:待审核3:审核通过 + /// + [Comment("-1:退回0:待处理1:保存待提交2:待审核6:审核通过")] + public int Status { get; set; } = 0; + + /// + /// 0:建档任务1:系统派发的巡查任务2:主动巡查任务 + /// + [Comment("0:建档任务1:系统派发的巡查任务2:主动巡查任务")] + public int? TaskType { get; set; } + + /// + /// 上报街道 + /// + [Comment("上报街道")] + public int? ReportRoad { get; set; } + + /// + /// 上报街道时间 + /// + [Comment("上报街道时间")] + public DateTime? ReportRoadTime { get; set; } + + /// + /// 上报区住建 + /// + [Comment("上报区住建")] + public int? ReportArea { get; set; } + + /// + /// 上报区住建时间 + /// + [Comment("上报区住建时间")] + public DateTime? ReportAreaTime { get; set; } + + /// + /// 上报备注 + /// + [Comment("上报备注")] + [MaxLength(1000)] + public string ReportRemark { get; set; } + + /// + /// 提交时间 + /// + [Comment("提交时间")] + public DateTime? SubmitTime { get; set; } + + /// + /// 最后提交时间 + /// + [Comment("最后提交时间")] + public DateTime? LastSubmitTime { get; set; } + + /// + /// 是否有效 + /// + [Comment("是否有效")] + public bool IsEnabled { get; set; } = true; + } +} diff --git a/Api/Ewide.Application/Entity/BsHouseTaskCheckRecord.cs b/Api/Ewide.Application/Entity/BsHouseTaskCheckRecord.cs new file mode 100644 index 0000000..5fdf4e1 --- /dev/null +++ b/Api/Ewide.Application/Entity/BsHouseTaskCheckRecord.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Ewide.Application +{ + [Table("bs_house_task_check_record")] + [Comment("房屋任务审核记录表")] + public class BsHouseTaskCheckRecord : Core.DEntityBase + { + /// + /// 任务Id + /// + [Comment("任务Id")] + [MaxLength(36)] + public string TaskId { get; set; } + + /// + /// 通过1;退回-1 + /// + [Comment("通过3;退回-1")] + public int PassOrBack { get; set; } + + /// + /// 审核内容 + /// + [Comment("审核内容")] + [MaxLength(500)] + public string Content { get; set; } + } +} diff --git a/Api/Ewide.Application/Entity/BsHouseTaskOptions.cs b/Api/Ewide.Application/Entity/BsHouseTaskOptions.cs new file mode 100644 index 0000000..c279a1b --- /dev/null +++ b/Api/Ewide.Application/Entity/BsHouseTaskOptions.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +namespace Ewide.Application +{ + [Table("bs_house_task_options")] + [Comment("房屋巡查任务选项表")] + public class BsHouseTaskOptions : Core.DEntityBase + { + /// + /// bs_house_code主键Id + /// + [Comment("bs_house_code主键Id")] + [MaxLength(36)] + [Required] + public string TaskId { get; set; } + + /// + /// 字典类型 + /// + [Comment("字典类型")] + [MaxLength(50)] + public string DictType { get; set; } + + /// + /// 字典code + /// + [Comment("字典code")] + [MaxLength(50)] + public string DictCode { get; set; } + + /// + /// 字典值 + /// + [Comment("字典值")] + [MaxLength(100)] + public string DictValue { get; set; } + + /// + /// 备注 + /// + [Comment("备注")] + [MaxLength(500)] + public string Remark { get; set; } + + /// + /// 附件 + /// + [Comment("附件")] + [MaxLength(2000)] + public string File { get; set; } + + /// + /// 是否选中 + /// + [Comment("是否选中")] + public bool? Checked { get; set; } + } +} diff --git a/Api/Ewide.Application/Entity/BsInspectionMemberRelation.cs b/Api/Ewide.Application/Entity/BsInspectionMemberRelation.cs new file mode 100644 index 0000000..adfbb61 --- /dev/null +++ b/Api/Ewide.Application/Entity/BsInspectionMemberRelation.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + [Table("bs_inspection_member_relation")] + [Comment("鉴定机构人员表")] + public class BsInspectionMemberRelation : Core.DEntityBase + { + [Comment("sys_user主键Id")] + [MaxLength(36)] + [Required] + public string SysUserId { get; set; } + + [Comment("bs_inspection_org主键Id")] + [MaxLength(36)] + [Required] + public string InspectionOrgId { get; set; } + } +} diff --git a/Api/Ewide.Application/Entity/BsInspectionOrg.cs b/Api/Ewide.Application/Entity/BsInspectionOrg.cs new file mode 100644 index 0000000..941b23f --- /dev/null +++ b/Api/Ewide.Application/Entity/BsInspectionOrg.cs @@ -0,0 +1,72 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + [Table("bs_inspection_org")] + [Comment("鉴定机构表")] + public class BsInspectionOrg : Core.DEntityBase + { + [Comment("名称")] + [MaxLength(100)] + public string Name { get; set; } + + [Comment("机构备案编号(YYYYNNN,如2021001)")] + [MaxLength(10)] + public string Code { get; set; } + + [Comment("社会统一信用代码")] + [MaxLength(50)] + public string CreditCode { get; set; } + + [Comment("地址")] + public string Address { get; set; } + + [Comment("联系人")] + [MaxLength(50)] + public string Contacts { get; set; } + + [Comment("联系人手机号")] + [MaxLength(50)] + public string ContactsPhone { get; set; } + + [Comment("法人代表")] + [MaxLength(50)] + public string LegalPerson { get; set; } + + [Comment("固定电话")] + [MaxLength(50)] + public string OrgTelephone { get; set; } + + [Comment("名录申请表")] + public string ApplicationFormFiles { get; set; } + + [Comment("机构营业执照和法人证书")] + public string OrgBusinessLicenseFiles { get; set; } + + [Comment("建设工程质量检测资质证书或检验检测机构资质认定证书")] + public string InspectionQualificationCertificateFiles { get; set; } + + [Comment("办公场所资料(房产证、不动产登记证或房屋租赁合同等)")] + public string OfficeInformationFiles { get; set; } + + [Comment("符合条件的从业人员职称证明、学历证明、劳动合同、社保证明等")] + public string EmployeeCertificateFiles { get; set; } + + [Comment("开展房屋安全鉴定工作必要的设备计量检定证书、校准证书")] + public string CalibrationCertificateFiles { get; set; } + + [Comment("其他资料")] + public string OtherFiles { get; set; } + + [Comment("状态 0登记中 1正常 -1已撤销 -2因红牌撤销")] + public InspectionOrgStatus Status { get; set; } + + } +} diff --git a/Api/Ewide.Application/Entity/BsInspectionOrgDirDetail.cs b/Api/Ewide.Application/Entity/BsInspectionOrgDirDetail.cs new file mode 100644 index 0000000..bab8b29 --- /dev/null +++ b/Api/Ewide.Application/Entity/BsInspectionOrgDirDetail.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + [Table("bs_inspection_org_dir_detail")] + [Comment("名录详情")] + public class BsInspectionOrgDirDetail : Core.DEntityBase + { + [Comment("名录Id")] + [MaxLength(36)] + [Required] + public string StageId { get; set; } + + [Comment("机构Id")] + [MaxLength(36)] + [Required] + public string OrgId { get; set; } + + [Comment("评分")] + [Required] + public int Score { get; set; } + + [Comment("评分结果 1优秀 2合格 3警告 4除名")] + [Required] + public int ScoreResult { get; set; } + } +} diff --git a/Api/Ewide.Application/Entity/BsInspectionOrgDirStage.cs b/Api/Ewide.Application/Entity/BsInspectionOrgDirStage.cs new file mode 100644 index 0000000..782b9b6 --- /dev/null +++ b/Api/Ewide.Application/Entity/BsInspectionOrgDirStage.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + [Table("bs_inspection_org_dir_stage")] + [Comment("名录期数")] + public class BsInspectionOrgDirStage : Core.DEntityBase + { + [Comment("名录期数")] + [Required] + [MaxLength(100)] + public string No { get; set; } + + [Comment("名录包含机构数量(冗余)")] + [Required] + public int OrgAmount { get; set; } + + [Comment("被除名机构数量(冗余)")] + [Required] + public int RemovedOrgAmount { get; set; } + + [Comment("同比上一年新入或回归机构数量(冗余)")] + [Required] + public int NewOrgAmount { get; set; } + + [Comment("状态 0未发布 1已发布")] + [Required] + public int Status { get; set; } + + [Comment("发布时间")] + public DateTime? PublishTime { get; set; } + } +} diff --git a/Api/Ewide.Application/Entity/BsInspectionOrgSetting.cs b/Api/Ewide.Application/Entity/BsInspectionOrgSetting.cs new file mode 100644 index 0000000..348dac7 --- /dev/null +++ b/Api/Ewide.Application/Entity/BsInspectionOrgSetting.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + [Table("bs_inspection_org_setting")] + [Comment("鉴定机构相关设置")] + public class BsInspectionOrgSetting : Core.DEntityBase + { + [Comment("设置类型 1申报时间范围")] + public InspectionOrgSettingType Type { get; set; } + + [Comment("设置")] + [Required] + public string Setting { get; set; } + } + + public class BsInspectionOrgSetting_DateRange + { + private DateTime _BeginDate { get; set; } + public DateTime BeginDate + { + get + { + return _BeginDate; + } + set + { + var date = new DateTime(DateTime.Now.Year, value.Month, value.Day); + _BeginDate = date; + } + } + + private DateTime _EndDate { get; set; } + public DateTime EndDate + { + get + { + return _EndDate; + } + set + { + var date = new DateTime(DateTime.Now.Year, value.Month, value.Day); + _EndDate = date; + } + } + } +} diff --git a/Api/Ewide.Application/Entity/BsInspectionOrgUpdate.cs b/Api/Ewide.Application/Entity/BsInspectionOrgUpdate.cs new file mode 100644 index 0000000..76b2d3f --- /dev/null +++ b/Api/Ewide.Application/Entity/BsInspectionOrgUpdate.cs @@ -0,0 +1,82 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + /// + /// 记录鉴定机构申请及修改, 在申请/修改时, 只记录在当前表内, 待审核通过将此表内容冗余到主表 + /// + [Table("bs_inspection_org_update")] + [Comment("鉴定机构修改记录表")] + public class BsInspectionOrgUpdate : Core.DEntityBase + { + [Comment("机构Id")] + [MaxLength(36)] + [Required] + public string OrgId { get; set; } + + [Comment("名称")] + [MaxLength(100)] + public string Name { get; set; } + + [Comment("社会统一信用代码")] + [MaxLength(50)] + public string CreditCode { get; set; } + + [Comment("地址")] + [Required] + public string Address { get; set; } + + [Comment("联系人")] + [MaxLength(50)] + public string Contacts { get; set; } + + [Comment("联系人手机号")] + [MaxLength(50)] + public string ContactsPhone { get; set; } + + [Comment("法人代表")] + [MaxLength(50)] + public string LegalPerson { get; set; } + + [Comment("固定电话")] + [MaxLength(50)] + public string OrgTelephone { get; set; } + + [Comment("名录申请表")] + public string ApplicationFormFiles { get; set; } + + [Comment("机构营业执照和法人证书")] + public string OrgBusinessLicenseFiles { get; set; } + + [Comment("建设工程质量检测资质证书或检验检测机构资质认定证书")] + public string InspectionQualificationCertificateFiles { get; set; } + + [Comment("办公场所资料(房产证、不动产登记证或房屋租赁合同等)")] + public string OfficeInformationFiles { get; set; } + + [Comment("符合条件的从业人员职称证明、学历证明、劳动合同、社保证明等")] + public string EmployeeCertificateFiles { get; set; } + + [Comment("开展房屋安全鉴定工作必要的设备计量检定证书、校准证书")] + public string CalibrationCertificateFiles { get; set; } + + [Comment("其他资料")] + public string OtherFiles { get; set; } + + [Comment("记录类型 1登记/回归 2修改")] + public InspectionOrgUpdateType Type { get; set; } + + [Comment("状态 -2放弃 -1退回 1审核中 2审核通过")] + public InspectionOrgUpdateStatus Status { get; set; } + + [Comment("退回理由/备注")] + public string Remark { get; set; } + } +} diff --git a/Api/Ewide.Application/Entity/README.md b/Api/Ewide.Application/Entity/README.md new file mode 100644 index 0000000..59bc7dd --- /dev/null +++ b/Api/Ewide.Application/Entity/README.md @@ -0,0 +1 @@ +/** 您的业务实体文件写在此文件夹下面 **/ diff --git a/Api/Ewide.Application/Enum/DataStatus.cs b/Api/Ewide.Application/Enum/DataStatus.cs new file mode 100644 index 0000000..d073a7f --- /dev/null +++ b/Api/Ewide.Application/Enum/DataStatus.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public enum DataStatus + { + /// + /// 审核退回 + /// + [Description("审核退回")] + Back = -1, + + /// + /// 初始未处理 + /// + [Description("初始未处理")] + Init = 0, + + /// + /// 暂存 + /// + [Description("暂存")] + TempSaved = 1, + + /// + /// 已保存,待提交 + /// + [Description("已保存,待提交")] + Saved = 2, + + /// + /// 已提交,待审核 + /// + [Description("已提交,待审核")] + Submited = 3, + + /// + /// 审核通过 + /// + [Description("审核通过")] + Passed = 6 + } +} diff --git a/Api/Ewide.Application/Enum/HouseLog.cs b/Api/Ewide.Application/Enum/HouseLog.cs new file mode 100644 index 0000000..29d707f --- /dev/null +++ b/Api/Ewide.Application/Enum/HouseLog.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public enum HouseLogType + { + [Description("创建房屋编码")] + CreateCode = 1, + + [Description("人员选房")] + SelectMember = 2, + + [Description("房屋建档")] + CreateInfo = 3, + + [Description("审核")] + Check = 4, + + [Description("审核通过")] + Agree = 5, + + [Description("审核退回")] + Disagree = 6 + + } + + public enum HouseLogStatus + { + [Description("待处理")] + Handle = 0, + + [Description("正在处理")] + Handling = 1, + + [Description("已处理")] + Handled = 2, + } +} diff --git a/Api/Ewide.Application/Enum/HouseManagerRole.cs b/Api/Ewide.Application/Enum/HouseManagerRole.cs new file mode 100644 index 0000000..bb0909b --- /dev/null +++ b/Api/Ewide.Application/Enum/HouseManagerRole.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public enum HouseManagerRole + { + /// + /// 市住建部门 + /// + CityManager, + + /// + /// 区住建部门 + /// + AreaManager, + + /// + /// 街道管理员 + /// + RoadManager, + + /// + /// 片区监管员 + /// + ZoneManager, + + /// + /// 房屋安全管理员 + /// + HouseSecurityManager + } +} diff --git a/Api/Ewide.Application/Enum/InspectionOrgEnum.cs b/Api/Ewide.Application/Enum/InspectionOrgEnum.cs new file mode 100644 index 0000000..a78adc3 --- /dev/null +++ b/Api/Ewide.Application/Enum/InspectionOrgEnum.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + /// + /// 鉴定机构相关设置 + /// + public enum InspectionOrgSettingType + { + /// + /// 开放时间范围 + /// + DateRange = 1 + } + + /// + /// 鉴定机构状态 + /// + public enum InspectionOrgStatus + { + /// + /// 因红牌撤销 + /// + Delisting = -2, + + /// + /// 撤销 + /// + Revoke = -1, + + /// + /// 登记中 + /// + Register = 0, + + /// + /// 正常 + /// + Normal = 1, + } + + /// + /// 鉴定机构登记/修改类型 + /// + public enum InspectionOrgUpdateType + { + /// + /// 登记/回归 + /// + Register = 1, + + /// + /// 修改 + /// + Update = 2, + } + + /// + /// 鉴定机构登记/修改审核状态 + /// + public enum InspectionOrgUpdateStatus + { + /// + /// 放弃 + /// + Close = -2, + + /// + /// 退回 + /// + Back = -1, + + /// + /// 审核中 + /// + Reviewing = 1, + + /// + /// 审核通过 + /// + Approved = 2, + } +} diff --git a/Api/Ewide.Application/Ewide.Application.csproj b/Api/Ewide.Application/Ewide.Application.csproj new file mode 100644 index 0000000..18a1132 --- /dev/null +++ b/Api/Ewide.Application/Ewide.Application.csproj @@ -0,0 +1,28 @@ + + + + net5.0 + 1701;1702;1591 + Ewide.Application.xml + + + + + + + + + + PreserveNewest + + + + + + + + + + + + diff --git a/Api/Ewide.Application/Ewide.Application.xml b/Api/Ewide.Application/Ewide.Application.xml new file mode 100644 index 0000000..65fc1be --- /dev/null +++ b/Api/Ewide.Application/Ewide.Application.xml @@ -0,0 +1,1647 @@ + + + + Ewide.Application + + + + + 单位类型,多选 + + + + + 单位名称 + + + + + 信息 + + + + + HouseCode主键ID + + + + + 幢名称 + + + + + 土地性质 + + + + + 结构类型 + + + + + 基础情况 + + + + + 抗震等级 + + + + + 竣工日期 + + + + + 设计使用年限 + + + + + 总层数 + + + + + 地上层数 + + + + + 地上商业层数开始 + + + + + 地上商业层数结束 + + + + + 地上车棚层开始 + + + + + 地上住宅层开始 + + + + + 地上住宅层结束 + + + + + 地下层数 + + + + + 总建筑面积 + + + + + 有无建筑幕墙 + + + + + 有无面砖 + + + + + 有无涂料 + + + + + 有无粉刷 + + + + + 电梯 + + + + + 总户数(户) + + + + + 房屋单元数 + + + + + 各单元每层户数(户) + + + + + 建设单位ID + + + + + 设计单位ID + + + + + 施工单位ID + + + + + 监理单位ID + + + + + 产权性质 + + + + + 总共套数 + + + + + 直管公房套数 + + + + + 自管公房套数 + + + + + 私房套数 + + + + + 商品房套数 + + + + + 房改房套数 + + + + + 拆迁安置房套数 + + + + + 其它套数 + + + + + 产权单位ID + + + + + 物业单位ID + + + + + 图纸资料存档处 + + + + + 其他图纸存档 + + + + + 立项文件 + + + + + 规划许可 + + + + + 竣工验收备案 + + + + + 监理资料 + + + + + 鉴定报告 + + + + + 其它资料 + + + + + 外立面照片 + + + + + 房屋等级 1,2,3,4,C,d + + + + + 房屋使用状态 + + + + + -1 退回(审核被退回的数据) 1、待建档(需要建档的数据未暂存未保存的)2、暂存(点击了暂存的数据)3、待提交(点击了保存的数据) 5、审核中6、审核通过 + + + + + 调查登记机构 + + + + + 纸质资料调查人 + + + + + 纸质资料审核人 + + + + + 主管部门 + + + + + bs_house_code主键Id + + + + + 巡查日期 + + + + + 巡查人姓名 + + + + + 沉降倾斜 + + + + + 沉降倾斜附件 + + + + + 其他情况 + + + + + 其他情况附件 + + + + + 初始等级 + + + + + 损坏等级 + + + + + 综合等级 + + + + + 房屋场地 + + + + + 相邻施工 + + + + + 化学侵蚀 + + + + + 结构拆改 + + + + + 加层改造 + + + + + 修缮加固 + + + + + 历史灾害 + + + + + 使用功能变更 + + + + + 其他调查内容 + + + + + 主要安全隐患综述 + + + + + 处理意见 + + + + + 处理意见备注 + + + + + 整改情况 + + + + + 整改情况备注 + + + + + 巡查结果:1正常,-1异常 + + + + + 巡查异常描述 + + + + + 任务人员ID + + + + + 任务截止时间 + + + + + 是否过期 + + + + + -1:退回0:待处理1:保存待提交2:待审核3:审核通过 + + + + + 0:建档任务1:系统派发的巡查任务2:主动巡查任务 + + + + + 上报街道 + + + + + 上报街道时间 + + + + + 上报区住建 + + + + + 上报区住建时间 + + + + + 上报备注 + + + + + 提交时间 + + + + + 最后提交时间 + + + + + 是否有效 + + + + + 任务Id + + + + + 通过1;退回-1 + + + + + 审核内容 + + + + + bs_house_code主键Id + + + + + 字典类型 + + + + + 字典code + + + + + 字典值 + + + + + 备注 + + + + + 附件 + + + + + 是否选中 + + + + + 记录鉴定机构申请及修改, 在申请/修改时, 只记录在当前表内, 待审核通过将此表内容冗余到主表 + + + + + 审核退回 + + + + + 初始未处理 + + + + + 暂存 + + + + + 已保存,待提交 + + + + + 已提交,待审核 + + + + + 审核通过 + + + + + 市住建部门 + + + + + 区住建部门 + + + + + 街道管理员 + + + + + 片区监管员 + + + + + 房屋安全管理员 + + + + + 鉴定机构相关设置 + + + + + 开放时间范围 + + + + + 鉴定机构状态 + + + + + 因红牌撤销 + + + + + 撤销 + + + + + 登记中 + + + + + 正常 + + + + + 鉴定机构登记/修改类型 + + + + + 登记/回归 + + + + + 修改 + + + + + 鉴定机构登记/修改审核状态 + + + + + 放弃 + + + + + 退回 + + + + + 审核中 + + + + + 审核通过 + + + + + 房屋编码相关 + + + + + 获取同一区域下的下一个编号 + + + + + + + 房屋详细信息 + + + + + 住宅查询 + + + + + 房屋流转日志 + + + + + 分页查询用户 + + + + + + + 增加用户 + + + + + + + 获取角色 + + + + + + + 获取用户拥有角色 + + + + + + + 删除用户 + + + + + + + 更新用户 + + + + + + + 查看用户 + + + + + + + 修改用户状态 + + + + + + + 授权用户数据范围 + + + + + + + 获取用户拥有数据 + + + + + + + 获取当前机构中所创建的默认角色 + 片区中的第一个帐号必定为片区监管员 + + + + + + + 获取可创建的角色列表 + + + + + + 项目管理相关服务 + + + + + 添加项目 + + + + + + + 删除项目 + + + + + + + 编辑项目 + + + + + + + 通过ID获取项目 + + + + + + + 分页查询 + + + + + + + + + + + + + + 获取项目下拉列表 + + + + + + + 选房相关 + + + + + 获取人员允许绑定的房屋编码列表 + + + + + + + 获取人员已经绑定的房屋编码列表 + + + + + + + 从人员选择房屋 + + + + + + + 房屋任务(巡查任务/建档任务) + + + + + 片区相关 + + + + + 获取片区列表 + + + + + + + 分页查询片区 + + + + + + + 根据用户Id获取所在片区的Id + + + + + + + 获取当前名录打分列表 + + + + + + 鉴定机构相关 + + + + + 获取可登记的时间范围 + + + + + + 获取当前用户所在机构 + + + + + + 获取最新备案编号 + + + + + + 获取当前用户所在鉴定机构信息及登记时间范围 + + + + + + 鉴定机构登记 + + + + + + + 鉴定机构申请修改 + + + + + + + 鉴定机构详情 + + + + + + + 登记审核通过 + + + + + + + 登记审核退回 + + + + + + + 获取审核记录 + + + + + + + 总建筑面积 + + + + + 总户数(户) + + + + + 房屋单元数 + + + + + 各单元每层户数(户) + + + + + 总层数 + + + + + 地上层数 + + + + + 地上商业层数开始 + + + + + 地上商业层数结束 + + + + + 地上车棚层开始 + + + + + 地上住宅层开始 + + + + + 地上住宅层结束 + + + + + 地下层数 + + + + + 产权性质 + + + + + 总共套数 + + + + + 直管公房套数 + + + + + 自管公房套数 + + + + + 私房套数 + + + + + 商品房套数 + + + + + 房改房套数 + + + + + 拆迁安置房套数 + + + + + 其它套数 + + + + + 房屋等级 1,2,3,4,C,d + + + + + 房屋使用状态 + + + + + 图纸资料存档处 + + + + + 其他图纸存档 + + + + + 立项文件 + + + + + 规划许可 + + + + + 竣工验收备案 + + + + + 监理资料 + + + + + 鉴定报告 + + + + + 其它资料 + + + + + 外立面照片 + + + + + 调查登记机构 + + + + + 纸质资料调查人 + + + + + 纸质资料审核人 + + + + + 主管部门 + + + + + 总建筑面积 + + + + + 总户数(户) + + + + + 房屋单元数 + + + + + 各单元每层户数(户) + + + + + 总层数 + + + + + 地上层数 + + + + + 地上商业层数开始 + + + + + 地上商业层数结束 + + + + + 地上车棚层开始 + + + + + 地上住宅层开始 + + + + + 地上住宅层结束 + + + + + 地下层数 + + + + + 产权性质 + + + + + 总共套数 + + + + + 直管公房套数 + + + + + 自管公房套数 + + + + + 私房套数 + + + + + 商品房套数 + + + + + 房改房套数 + + + + + 拆迁安置房套数 + + + + + 其它套数 + + + + + 房屋等级 1,2,3,4,C,d + + + + + 房屋使用状态 + + + + + 图纸资料存档处 + + + + + 其他图纸存档 + + + + + 立项文件 + + + + + 规划许可 + + + + + 竣工验收备案 + + + + + 监理资料 + + + + + 鉴定报告 + + + + + 其它资料 + + + + + 外立面照片 + + + + + 调查登记机构 + + + + + 纸质资料调查人 + + + + + 纸质资料审核人 + + + + + 主管部门 + + + + + 任务Id + + + + + 通过3;退回-1 + + + + + 审核内容 + + + + + 沉降倾斜 + + + + + 沉降倾斜附件 + + + + + 其他情况 + + + + + 其他情况附件 + + + + + 房屋场地 + + + + + 相邻施工 + + + + + 化学侵蚀 + + + + + 结构拆改 + + + + + 加层改造 + + + + + 修缮加固 + + + + + 历史灾害 + + + + + 使用功能变更 + + + + + 其他调查内容 + + + + + 主要安全隐患综述 + + + + + 处理意见 + + + + + 处理意见备注 + + + + + 整改情况 + + + + + 整改情况备注 + + + + + 巡查结果:1正常,-1异常 + + + + + 上报街道 + + + + + 上报街道时间 + + + + + 上报区住建 + + + + + 上报区住建时间 + + + + + 上报备注 + + + + + 沉降倾斜 + + + + + 沉降倾斜附件 + + + + + 其他情况 + + + + + 其他情况附件 + + + + + 初始等级 + + + + + 损坏等级 + + + + + 综合等级 + + + + + 房屋场地 + + + + + 相邻施工 + + + + + 化学侵蚀 + + + + + 结构拆改 + + + + + 加层改造 + + + + + 修缮加固 + + + + + 历史灾害 + + + + + 使用功能变更 + + + + + 其他调查内容 + + + + + 主要安全隐患综述 + + + + + 处理意见 + + + + + 处理意见备注 + + + + + 整改情况 + + + + + 整改情况备注 + + + + + 巡查结果:1正常,-1异常 + + + + + 巡查异常描述 + + + + + 上报街道 + + + + + 上报街道时间 + + + + + 上报区住建 + + + + + 上报区住建时间 + + + + + 上报备注 + + + + + 提交时间 + + + + + 最后提交时间 + + + + + 是否有效 + + + + + 所属街道 + + + + + 名称 + + + + + 机构Id + + + + diff --git a/Api/Ewide.Application/README.md b/Api/Ewide.Application/README.md new file mode 100644 index 0000000..11a79cd --- /dev/null +++ b/Api/Ewide.Application/README.md @@ -0,0 +1,3 @@ +1、原则上服务应该放在Application层次,考虑将咱自己的业务层直接写在Application里面好些,后续升级后,咱大家直接升级就行了,减少冲突! +2、系统默认ORM为EF Core,如果觉得不趁手,可以自行更换 +3、在此应用层默认集成了SqlSugar,其他ORM类同,可以多个ORM并行开发,熟悉哪个用哪个 diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseCode/Dto/HouseCodeInput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseCode/Dto/HouseCodeInput.cs new file mode 100644 index 0000000..d105eda --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseCode/Dto/HouseCodeInput.cs @@ -0,0 +1,65 @@ +using Ewide.Core; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseCodeInput + { + } + + public class AddHouseCodeInput : PageInputBase + { + public string HouseCode { get; set; } + [Required(ErrorMessage = "行政区域编码不能为空")] + public string AreaCode { get; set; } + [Required(ErrorMessage = "项目Id不能为空")] + public string ProjectId { get; set; } + [Required(ErrorMessage = "房屋编号不能为空")] + public int No { get; set; } + [Required(ErrorMessage = "片区Id不能为空")] + public string ZoneId { get; set; } + [Required(ErrorMessage = "房屋地址不能为空")] + public string Address { get; set; } + [Required(ErrorMessage = "房屋性质不能为空")] + public int Type { get; set; } + [Required(ErrorMessage = "所属行业不能为空")] + public int Industry { get; set; } + [Required(ErrorMessage = "经度不能为空")] + public string Lng { get; set; } + [Required(ErrorMessage = "纬度不能为空")] + public string Lat { get; set; } + } + + public class EditHouseCodeInput : AddHouseCodeInput + { + [Required(ErrorMessage = "房屋编码Id不能为空")] + public string Id { get; set; } + } + + public class DeleteHouseCodeInput + { + [Required(ErrorMessage = "房屋编码ID不可为空")] + public string Id { get; set; } + } + + public class QueryHouseCodeInput : PageInputBase + { + public string HouseCode { get; set; } + public int? No { get; set; } + public string Address { get; set; } + public string ProjectId { get; set; } + public string ZoonId { get; set; } + public int Type { get; set; } + } + + public class GetHouseCodeInput + { + [Required(ErrorMessage = "房屋编码ID不可为空")] + public string Id { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseCode/Dto/HouseCodeOutput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseCode/Dto/HouseCodeOutput.cs new file mode 100644 index 0000000..bb691f8 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseCode/Dto/HouseCodeOutput.cs @@ -0,0 +1,43 @@ +using Ewide.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseCodeOutput + { + public string Id { get; set; } + public string Address { get; set; } + public string ProjectId { get; set; } + public string ZoneId { get; set; } + public int Type { get; set; } + public string AreaName { get; set; } + public string RoadName { get; set; } + public string CommName { get; set; } + public string ZoneName { get; set; } + public string ProjectNote { get; set; } + public string FullProjName { get; set; } + public string HouseCode { get; set; } + public int No { get; set; } + public string Lng { get; set; } + public string Lat { get; set; } + } + + public class GetHouseCodeOutput + { + public string Id { get; set; } + public string HouseCode { get; set; } + public int Type { get; set; } + public int Industry { get; set; } + public string AreaCode { get; set; } + public string ProjectId { get; set; } + public int No { get; set; } + public string ZoneId { get; set; } + public string Address { get; set; } + public string Lng { get; set; } + public string Lat { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseCode/HouseCodeService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseCode/HouseCodeService.cs new file mode 100644 index 0000000..8175ddd --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseCode/HouseCodeService.cs @@ -0,0 +1,123 @@ +using Ewide.Application.Entity; +using Ewide.Core; +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Threading.Tasks; +using Dapper; +using Ewide.Core.Extension; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Collections.Generic; + +namespace Ewide.Application.Service.HouseCode +{ + /// + /// 房屋编码相关 + /// + [ApiDescriptionSettings(Name = "HouseCode", Order = 180)] + public class HouseCodeService : IHouseCodeService, IDynamicApiController, ITransient + { + private readonly IRepository _houseCodeRep; + private readonly IDapperRepository _dapperRepository; + + private readonly IUserManager _userManager; + private readonly IHouseLogService _houseLogService; + + public HouseCodeService(IRepository HouseCodeRep, IDapperRepository dapperRepository, IUserManager userManager, IHouseLogService houseLogService) + { + _houseCodeRep = HouseCodeRep; + _dapperRepository = dapperRepository; + + _userManager = userManager; + _houseLogService = houseLogService; + } + + [HttpPost("/houseCode/add")] + [UnitOfWork] + public async Task AddHouseCode(AddHouseCodeInput input) + { + var houseProjectInfoRep = Db.GetRepository(); + var houseProject = await houseProjectInfoRep.FirstOrDefaultAsync(p => p.Id == input.ProjectId); + if (houseProject == null) throw Oops.Oh("项目参数有误,添加失败"); + + var areaCodeRep = Db.GetRepository(); + var areaCode = await areaCodeRep.DetachedEntities.FirstOrDefaultAsync(a => a.Code == input.AreaCode && a.LevelType == 4); + if(areaCode == null) throw Oops.Oh("区域编码有误,添加失败"); + input.HouseCode = areaCode.AdCode + houseProject.Sort.ToString().PadLeft(3, '0') + input.No.ToString().PadLeft(3, '0'); + var id = System.Guid.NewGuid().ToString().ToLower(); + var houseCode = input.Adapt(); + houseCode.Id = id; + var isExist = await _houseCodeRep.AnyAsync(p => p.HouseCode == houseCode.HouseCode); + if (isExist) throw Oops.Oh("房屋编码已存在,不可重复添加"); + await houseCode.InsertAsync(); + + // 添加到流转日志 + var sort = await _houseLogService.AddThenDone(id, _userManager.User, HouseLogType.CreateCode); + sort = await _houseLogService.Add(id, _userManager.User, HouseLogType.SelectMember, sort: sort); + } + + [HttpPost("/houseCode/edit")] + public async Task EditHouseCode(EditHouseCodeInput input) + { + var houseCode = input.Adapt(); + await houseCode.UpdateExcludeAsync(new[] { nameof(BsHouseCode.HouseCode) }, true); + } + + [HttpPost("/houseCode/delete")] + public async Task DeleteHouseCode(DeleteHouseCodeInput input) + { + var houseCode = _houseCodeRep.FirstOrDefault(p => p.Id == input.Id); + await houseCode.DeleteNowAsync(); + } + + [HttpPost("/houseCode/page")] + public async Task QueryPage([FromBody] QueryHouseCodeInput input) + { + var sql = @"SELECT +HC.*,AA.Name AreaName,RA.Name RoadName,CA.Name CommName,Proj.AreaCode,Proj.Note,CONCAT(Proj.`Name`, IFNULL(CONCAT('(',Proj.Note,')'), '')) FullProjName +FROM bs_house_code HC +LEFT JOIN bs_house_projectinfo Proj ON Proj.Id=HC.ProjectId +LEFT JOIN sys_area_code CA ON CA.Code = Proj.AreaCode +LEFT JOIN sys_area_code RA ON RA.AdCode = SUBSTR(CA.AdCode,1,9) +LEFT JOIN sys_area_code AA ON AA.AdCode = SUBSTR(CA.AdCode,1,6) "; + return await _dapperRepository.QueryPageDataDynamic(sql, input, filterFields: new string[] {"Type", "Address", "HouseCode","AreaCode"}); + } + + [HttpGet("/houseCode/detail")] + public async Task GetHouserCode([FromQuery] GetHouseCodeInput input) + { + var houseCode = await _houseCodeRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == input.Id); + var areaCode = (await Db.GetRepository().DetachedEntities.FirstOrDefaultAsync(p => p.Id == houseCode.ProjectId)).AreaCode; + var result = houseCode.Adapt(); + result.AreaCode = areaCode; + return result; + } + + /// + /// 获取同一区域下的下一个编号 + /// + /// + /// + [HttpGet("/houseCode/getNextNoByCode")] + public async Task GetNextNoByFullNumber([Required] string projectId) + { + var areaCodeRep = Db.GetRepository(); + var houseProjectInfoRep = Db.GetRepository(); + var houseProject = await houseProjectInfoRep.FirstOrDefaultAsync(p => p.Id == projectId); + if(houseProject == null) throw Oops.Oh("项目参数有误,房屋编码生成失败"); + //取到社区编码 + var commAreaCode = await areaCodeRep.FirstOrDefaultAsync(a => a.Code == houseProject.AreaCode && a.LevelType == 4); + if(commAreaCode == null) throw Oops.Oh("项目区域编码有误,房屋编码生成失败"); + var maxNo = await _houseCodeRep.DetachedEntities + .Where(h => h.HouseCode.Contains(commAreaCode.AdCode + houseProject.Sort.ToString().PadLeft(3, '0'))) + .MaxAsync(h => (int?)h.No); + return maxNo.GetValueOrDefault(0) + 1; + } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseCode/IHouseCodeService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseCode/IHouseCodeService.cs new file mode 100644 index 0000000..c1e1e4f --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseCode/IHouseCodeService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service.HouseCode +{ + public interface IHouseCodeService + { + Task AddHouseCode(AddHouseCodeInput input); + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseCompany/Dto/HouseCompanyInput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseCompany/Dto/HouseCompanyInput.cs new file mode 100644 index 0000000..95fcd6e --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseCompany/Dto/HouseCompanyInput.cs @@ -0,0 +1,61 @@ +using Ewide.Core; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseCompanyInput + { + [Required(ErrorMessage = "Id不能为空")] + public string Id { get; set; } + } + + public class HouseCompanyInfoInput + { + [Required(ErrorMessage = "信息名称不能为空")] + public string Name { get; set; } + [Required(ErrorMessage = "信息内容不能为空")] + public string Value { get; set; } + } + + public class HouseCompanyPageInput : PageInputBase {} + + public class HouseCompanyAddInput + { + [Required(ErrorMessage = "名称不能为空")] + public virtual string Name { get; set; } + + private string _Type { get; set; } + + [Required(ErrorMessage = "类型不能为空")] + public virtual string Type + { + get + { + return _Type; + } + set + { + _Type = String.Join(",", value.Split(',').Select(p => $"[{p}]")); + } + } + + public virtual List Info { get; set; } + } + + public class HouseCompanyEditInput : HouseCompanyAddInput + { + [Required(ErrorMessage = "Id不能为空")] + public string Id { get; set; } + } + + public class HouseCompanyListInput + { + [Required(ErrorMessage = "类型不能为空")] + public string Type { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseCompany/Dto/HouseCompanyOutput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseCompany/Dto/HouseCompanyOutput.cs new file mode 100644 index 0000000..8692722 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseCompany/Dto/HouseCompanyOutput.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseCompanyInfoOutput + { + public string Name { get; set; } + public string Value { get; set; } + } + + public class HouseCompanyDetailOutput + { + public string Id { get; set; } + public string Name { get; set; } + private string _Type { get; set; } + public string Type + { + get + { + return _Type; + } + set + { + _Type = String.Join(",", value.Split(',').Select(p => p.Replace("[", "").Replace("]", ""))); + } + } + public List Info { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseCompany/HouseCompanyService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseCompany/HouseCompanyService.cs new file mode 100644 index 0000000..dc99898 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseCompany/HouseCompanyService.cs @@ -0,0 +1,101 @@ +using Ewide.Core.Extension; +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + [ApiDescriptionSettings(Name = "HouseCompany")] + public class HouseCompanyService : IHouseCompanyService, IDynamicApiController, ITransient + { + private readonly IRepository _bsHouseCompanyRep; + + public HouseCompanyService(IRepository bsHouseCompanyRep) + { + _bsHouseCompanyRep = bsHouseCompanyRep; + } + + [HttpPost("/houseCompany/page")] + public async Task Page([FromBody] HouseCompanyPageInput input) + { + var config = new TypeAdapterConfig().ForType() + .Map(target => target.Info, src => JsonConvert.DeserializeObject>(src.Info)) + .Config; + return await _bsHouseCompanyRep.DetachedEntities.ToPageData(input, config); + } + + [HttpPost("/houseCompany/add")] + public async Task Add([FromBody] HouseCompanyAddInput input) + { + var info = JsonConvert.SerializeObject(input.Info); + var config = new TypeAdapterConfig().ForType() + .Map(target => target.Info, src => JsonConvert.SerializeObject(src.Info)) + .Config; + var company = input.Adapt(config); + await company.InsertAsync(); + } + + [HttpPost("/houseCompany/edit")] + public async Task Edit([FromBody] HouseCompanyEditInput input) + { + var config = new TypeAdapterConfig().ForType() + .Map(target => target.Info, src => JsonConvert.SerializeObject(src.Info)) + .Config; + var company = input.Adapt(config); + await company.UpdateAsync(); + } + + [HttpPost("/houseCompany/delete")] + public async Task Delete([FromBody] HouseCompanyInput input) + { + var company = await _bsHouseCompanyRep.FirstOrDefaultAsync(p => p.Id.Equals(input.Id)); + await company.DeleteAsync(); + } + + [HttpGet("/houseCompany/detail")] + public async Task Detail([FromQuery] HouseCompanyInput input) + { + var company = await _bsHouseCompanyRep.FirstOrDefaultAsync(p => p.Id.Equals(input.Id)); + var config = new TypeAdapterConfig().ForType() + .Map(target => target.Info, src => JsonConvert.DeserializeObject>(src.Info)) + .Config; + var output = company.Adapt(config); + return output; + } + + [HttpGet("/houseCompany/list")] + public async Task List([FromQuery] HouseCompanyListInput input) + { + var types = input.Type.Split(','); + var result = new List(); + var config = new TypeAdapterConfig().ForType() + .Map(target => target.Info, src => JsonConvert.DeserializeObject>(src.Info)) + .Config; + foreach (var type in types) + { + var list = (await _bsHouseCompanyRep.DetachedEntities + .Where(p => EF.Functions.Like(p.Type, $"%[{type.Trim()}]%")) + .OrderBy(p => p.Name) + .ToListAsync()) + .Select(p => + { + var output = p.Adapt(config); + return output; + }); + result.AddRange(list); + } + + return result.Distinct(); + } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseCompany/IHouseCompanyService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseCompany/IHouseCompanyService.cs new file mode 100644 index 0000000..94eac4e --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseCompany/IHouseCompanyService.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public interface IHouseCompanyService + { + Task Page([FromBody] HouseCompanyPageInput input); + Task Add([FromBody] HouseCompanyAddInput input); + Task Edit([FromBody] HouseCompanyEditInput input); + Task Delete([FromBody] HouseCompanyInput input); + Task Detail([FromQuery] HouseCompanyInput input); + Task List([FromQuery] HouseCompanyListInput input); + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseInfo/Dto/HouseInfoInput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseInfo/Dto/HouseInfoInput.cs new file mode 100644 index 0000000..6977a3f --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseInfo/Dto/HouseInfoInput.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseInfoInput + { + #region 建筑物基本信息building + public string BuildingName { get; set; } + + public int? LandAttribute { get; set; } + + public int? StructureType { get; set; } + + public int? SeismicGrade { get; set; } + + public int? BaseInfo { get; set; } + + public string InsulationMaterial { get; set; } + + public string WallMaterial { get; set; } + + public string FireproofGrade { get; set; } + public int? CurtainWall { get; set; } + public int? FaceBrick { get; set; } + public int? WhiteWash { get; set; } + public int? Coating { get; set; } + public string Elevator { get; set; } + public DateTime? CompletedDate { get; set; } + public int? UsefulYear { get; set; } + /// + /// 总建筑面积 + /// + public float? TotalArea { get; set; } + /// + /// 总户数(户) + /// + public int? HouseHolds { get; set; } + + /// + /// 房屋单元数 + /// + public int? Units { get; set; } + + /// + /// 各单元每层户数(户) + /// + public int? UnitFloorHolds { get; set; } + + /// + /// 总层数 + /// + public int? TotalFloor { get; set; } + + /// + /// 地上层数 + /// + public int? LandFloorCount { get; set; } + + /// + /// 地上商业层数开始 + /// + public int? LandBsFloorStart { get; set; } + + /// + /// 地上商业层数结束 + /// + public int? LandBsFloorEnd { get; set; } + + /// + /// 地上车棚层开始 + /// + public int? LandBikeFloorStart { get; set; } + + /// + /// 地上住宅层开始 + /// + public int? LandResidenceFloorStart { get; set; } + + /// + /// 地上住宅层结束 + /// + public int? LandResidenceFloorEnd { get; set; } + + /// + /// 地下层数 + /// + public int? UnderFloorCount { get; set; } + #endregion + #region 权属情况ownership + /// + /// 产权性质 + /// + public string PropertyRights { get; set; } + + /// + /// 总共套数 + /// + public int? HouseCount { get; set; } + /// + /// 直管公房套数 + /// + public int? StraightHouseCount { get; set; } + + /// + /// 自管公房套数 + /// + public int? SelfHouseCount { get; set; } + + /// + /// 私房套数 + /// + public int? PrivateHouseCount { get; set; } + + /// + /// 商品房套数 + /// + public int? BusinessCount { get; set; } + + /// + /// 房改房套数 + /// + public int? ChangeHouseCount { get; set; } + + /// + /// 拆迁安置房套数 + /// + public int? ResettlementHouseCount { get; set; } + + /// + /// 其它套数 + /// + public int? OtherCount { get; set; } + + #endregion + #region 管理情况manager + #endregion + + #region 鉴定治理identification + + /// + /// 房屋等级 1,2,3,4,C,d + /// + public int? HouseGrade { get; set; } + + /// + /// 房屋使用状态 + /// + public int? HouseUsedStatus { get; set; } + #endregion + + #region 图纸资料存档处drawing + + /// + /// 图纸资料存档处 + /// + public string DrawingMaterial { get; set; } + + /// + /// 其他图纸存档 + /// + public string DrawingMaterialText { get; set; } + #endregion + + #region 相关附件资料attachments + /// + /// 立项文件 + /// + public string AnEntryDocument { get; set; } + + /// + /// 规划许可 + /// + public string PlanningPermission { get; set; } + + /// + /// 竣工验收备案 + /// + public string CompletionRecord { get; set; } + + /// + /// 监理资料 + /// + public string MonitorDocument { get; set; } + + /// + /// 鉴定报告 + /// + public string IdentificationReport { get; set; } + + /// + /// 其它资料 + /// + public string OtherDocument { get; set; } + #endregion + + #region 建筑概貌aspect + /// + /// 外立面照片 + /// + public string FacadePhoto { get; set; } + #endregion + + #region 调查单位unit + /// + /// 调查登记机构 + /// + public string InvestigateAgency { get; set; } + + /// + /// 纸质资料调查人 + /// + public string InvestigateUser { get; set; } + + /// + /// 纸质资料审核人 + /// + public string OfflineAuditor { get; set; } + + /// + /// 主管部门 + /// + public string CompetentDepartment { get; set; } + #endregion + + public int State { get; set; } + } + + public class HouseInfoInputSave + { + public HouseCodeOutput houseCode { get; set; } + public HouseInfoInput houseInfo { get; set; } + public EditHouseTaskInput PatrolInfo { get; set; } + public HouseTaskCheckRecordInput TaskCheckRecord { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseInfo/Dto/HouseInfoOutput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseInfo/Dto/HouseInfoOutput.cs new file mode 100644 index 0000000..104f44d --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseInfo/Dto/HouseInfoOutput.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseInfoOutput + { + #region 建筑物基本信息building + public string BuildingName { get; set; } + + public int? LandAttribute { get; set; } + + public int? StructureType { get; set; } + + public int? SeismicGrade { get; set; } + + public int? BaseInfo { get; set; } + + public string InsulationMaterial { get; set; } + + public string WallMaterial { get; set; } + + public string FireproofGrade { get; set; } + public int? CurtainWall { get; set; } + public int? FaceBrick { get; set; } + public int? WhiteWash { get; set; } + public int? Coating { get; set; } + public string Elevator { get; set; } + public DateTime? CompletedDate { get; set; } + public int? UsefulYear { get; set; } + /// + /// 总建筑面积 + /// + public float? TotalArea { get; set; } + /// + /// 总户数(户) + /// + public int? HouseHolds { get; set; } + + /// + /// 房屋单元数 + /// + public int? Units { get; set; } + + /// + /// 各单元每层户数(户) + /// + public int? UnitFloorHolds { get; set; } + + /// + /// 总层数 + /// + public int? TotalFloor { get; set; } + + /// + /// 地上层数 + /// + public int? LandFloorCount { get; set; } + + /// + /// 地上商业层数开始 + /// + public int? LandBsFloorStart { get; set; } + + /// + /// 地上商业层数结束 + /// + public int? LandBsFloorEnd { get; set; } + + /// + /// 地上车棚层开始 + /// + public int? LandBikeFloorStart { get; set; } + + /// + /// 地上住宅层开始 + /// + public int? LandResidenceFloorStart { get; set; } + + /// + /// 地上住宅层结束 + /// + public int? LandResidenceFloorEnd { get; set; } + + /// + /// 地下层数 + /// + public int? UnderFloorCount { get; set; } + #endregion + #region 权属情况ownership + /// + /// 产权性质 + /// + public string PropertyRights { get; set; } + + /// + /// 总共套数 + /// + public int? HouseCount { get; set; } + /// + /// 直管公房套数 + /// + public int? StraightHouseCount { get; set; } + + /// + /// 自管公房套数 + /// + public int? SelfHouseCount { get; set; } + + /// + /// 私房套数 + /// + public int? PrivateHouseCount { get; set; } + + /// + /// 商品房套数 + /// + public int? BusinessCount { get; set; } + + /// + /// 房改房套数 + /// + public int? ChangeHouseCount { get; set; } + + /// + /// 拆迁安置房套数 + /// + public int? ResettlementHouseCount { get; set; } + + /// + /// 其它套数 + /// + public int? OtherCount { get; set; } + + #endregion + #region 管理情况manager + #endregion + + #region 鉴定治理identification + + /// + /// 房屋等级 1,2,3,4,C,d + /// + public int? HouseGrade { get; set; } + + /// + /// 房屋使用状态 + /// + public int? HouseUsedStatus { get; set; } + #endregion + + #region 图纸资料存档处drawing + + /// + /// 图纸资料存档处 + /// + public string DrawingMaterial { get; set; } + + /// + /// 其他图纸存档 + /// + public string DrawingMaterialText { get; set; } + #endregion + + #region 相关附件资料attachments + /// + /// 立项文件 + /// + public string AnEntryDocument { get; set; } + + /// + /// 规划许可 + /// + public string PlanningPermission { get; set; } + + /// + /// 竣工验收备案 + /// + public string CompletionRecord { get; set; } + + /// + /// 监理资料 + /// + public string MonitorDocument { get; set; } + + /// + /// 鉴定报告 + /// + public string IdentificationReport { get; set; } + + /// + /// 其它资料 + /// + public string OtherDocument { get; set; } + #endregion + + #region 建筑概貌aspect + /// + /// 外立面照片 + /// + public string FacadePhoto { get; set; } + #endregion + public int State { get; set; } + #region 调查单位unit + /// + /// 调查登记机构 + /// + public string InvestigateAgency { get; set; } + + /// + /// 纸质资料调查人 + /// + public string InvestigateUser { get; set; } + + /// + /// 纸质资料审核人 + /// + public string OfflineAuditor { get; set; } + + /// + /// 主管部门 + /// + public string CompetentDepartment { get; set; } + #endregion + } + + public class HouseInfoOutputForDetailPage + { + #region header + public HouseCodeOutput HouseCode { get; set; } + #endregion + + public HouseInfoOutput HouseInfo { get; set; } + public HouseTaskOutput PatrolInfo { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseInfo/HouseInfoService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseInfo/HouseInfoService.cs new file mode 100644 index 0000000..a5b2d5e --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseInfo/HouseInfoService.cs @@ -0,0 +1,208 @@ +using Dapper; +using Ewide.Core; +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service.HouseSafety.HouseInfo +{ + /// + /// 房屋详细信息 + /// + [ApiDescriptionSettings(Name = "HouseInfo", Order = 200)] + public class HouseInfoService : IHouseInfoService, IDynamicApiController, ITransient + { + + private readonly IRepository _houseCodeRep; + private readonly IRepository _houseInfoRep; + private readonly IRepository _houseTaskRep; + private readonly IRepository _sysUserRep; + private readonly IUserManager _userManager; + private readonly IDapperRepository _dapperRepository; + + private readonly IHouseLogService _houseLogService; + + + public HouseInfoService( + IRepository HouseCodeRep, + IRepository HouseInfoRep, + IRepository HouseTaskRep, + IRepository sysUserRep, + IUserManager userManager, IDapperRepository dapperRepository, IHouseLogService houseLogService) + { + _houseCodeRep = HouseCodeRep; + _houseInfoRep = HouseInfoRep; + _houseTaskRep = HouseTaskRep; + _sysUserRep = sysUserRep; + _userManager = userManager; + _dapperRepository = dapperRepository; + + _houseLogService = houseLogService; + } + + [HttpPost("/houseInfo/save")] + [UnitOfWork] + [AllowAnonymous] + public async Task Save([FromBody] HouseInfoInputSave input) + { + await InputDataProcess(input, DataStatus.Saved); + } + + [HttpPost("/houseInfo/submitToCheck")] + [UnitOfWork] + [AllowAnonymous] + public async Task SubmitToCheck([FromBody] HouseInfoInputSave input) + { + await InputDataProcess(input,DataStatus.Submited); + } + + [HttpPost("/houseInfo/check")] + [UnitOfWork] + public async Task Check([FromBody] HouseInfoInputSave input) + { + await InputDataProcess(input, input.TaskCheckRecord.PassOrBackDataStatus ); + } + + [HttpGet("/houseInfo/getByTaskId")] + [UnitOfWork] + public async Task GetByTaskId([Required] string taskId) + { + //获取任务实体 + var houseTask = await _houseTaskRep.DetachedEntities.FirstOrDefaultAsync(t=>t.Id == taskId); + if(houseTask == null) throw Oops.Oh("任务不存在"); + + //获取房屋编码信息 + var houseCodeOutputAsync = await _dapperRepository.QueryAsync( + @"SELECT +HC.Id,HC.Address,HC.ProjectId,HC.ZoneId,HC.Type,AA.Name AreaName,RA.Name RoadName,CA.Name CommName,O.Name ZoneName,Proj.Note ProjectNote,CONCAT(Proj.`Name`, IFNULL(CONCAT('(',Proj.Note,')'), '')) FullProjName,HC.HouseCode,HC.Lng,HC.Lat,HC.No +FROM bs_house_code HC +LEFT JOIN bs_house_projectinfo Proj ON Proj.Id=HC.ProjectId +LEFT JOIN sys_org O ON HC.ZoneId = O.Id +LEFT JOIN sys_area_code CA ON CA.Code = Proj.AreaCode +LEFT JOIN sys_area_code RA ON RA.AdCode = SUBSTR(CA.AdCode,1,9) +LEFT JOIN sys_area_code AA ON AA.AdCode = SUBSTR(CA.AdCode,1,6) +WHERE HC.Id=@HouseCodeId", new { houseTask.HouseCodeId } + ); + var houseCodeOutput= houseCodeOutputAsync.SingleOrDefault(); + if (houseCodeOutput == null) throw Oops.Oh("任务已失效:房屋编码不存在,请重新加载任务列表"); + + //获取当前登录账号 + var currentUser = await _userManager.CheckUserAsync(); + + //查询房屋详细信息是否存在 + var houseInfo = await _houseInfoRep.DetachedEntities.FirstOrDefaultAsync(h => h.HouseCodeId == houseTask.HouseCodeId); + var houseInfoOutputForDetailPage = new HouseInfoOutputForDetailPage + { + HouseCode = houseCodeOutput + }; + + var taskOutput = houseTask.Adapt(); + taskOutput.PatrolDate = houseTask.PatrolDate.GetValueOrDefault(DateTime.Now); + taskOutput.PatrolUser = String.IsNullOrEmpty(houseTask.PatrolUser) ? currentUser.Name : houseTask.PatrolUser; + houseInfoOutputForDetailPage.PatrolInfo = taskOutput; + + if (houseInfo == null || houseInfo.State <= (int)DataStatus.Submited) + { + // 未建档或者当前数据未提交时打开,标记为已读 + await _houseLogService.Read(houseCodeOutput.Id); + } + + if (houseInfo == null) + { + houseInfoOutputForDetailPage.HouseInfo = new HouseInfoOutput(); + return houseInfoOutputForDetailPage; + } + + var houseInfoOutput = (await _houseInfoRep.DetachedEntities.FirstOrDefaultAsync(p => p.HouseCodeId == houseTask.HouseCodeId)).Adapt(); + houseInfoOutputForDetailPage.HouseInfo = houseInfoOutput; + + return houseInfoOutputForDetailPage; + } + + [NonAction] + [UnitOfWork] + public async Task InputDataProcess(HouseInfoInputSave input,DataStatus dataStatus = DataStatus.Init) + { + //房屋编码地址/坐标单独更新 + var houseCode = input.houseCode.Adapt(); + await houseCode.UpdateIncludeAsync(new[] { nameof(BsHouseCode.Address), nameof(BsHouseCode.Lng), nameof(BsHouseCode.Lat) }, true); + + //获取房屋详情实体,判断是新增还是更新 + var houseEntity = await _houseInfoRep.DetachedEntities.FirstOrDefaultAsync(h => h.HouseCodeId == input.houseCode.Id); + + //建档审核通过的房屋数据修改时,对应的建档任务Task不处理 + var houseTask = await _houseTaskRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == input.PatrolInfo.Id); + if (houseEntity == null || houseEntity.State != 6) + { + var _houseTask = input.PatrolInfo.Adapt(); + _houseTask.HouseCodeId = input.houseCode.Id; + //任务没有暂存状态,其他状态与HouseInfo的State一致 + _houseTask.Status = dataStatus == DataStatus.TempSaved ? (int)DataStatus.Saved : (int)dataStatus; + await _houseTask.UpdateExcludeAsync(new string[] { nameof(BsHouseTask.TaskType) }, ignoreNullValues: true); + } + //判断房屋建档状态 + //dataStatus == DataStatus.Saved 若是保存操作 则判断是新增/更新 + //判断是新增/更新 根据传入参数dataStatus确定 + input.houseInfo.State = dataStatus == DataStatus.Saved ? (houseEntity == null ? (int)DataStatus.Saved : houseEntity.State) : (int)dataStatus; + var houseInfo = input.houseInfo.Adapt(); + houseInfo.HouseCodeId = input.houseCode.Id; + + if (houseEntity == null) + { + houseInfo.Id = Guid.NewGuid().ToString(); + await houseInfo.InsertAsync(); + } + else + { + houseInfo.Id = houseEntity.Id; + await houseInfo.UpdateExcludeAsync(new[] { nameof(BsHouseInfo.HouseGrade) }, true); + } + + int? logSort = null; + + if (dataStatus == DataStatus.Submited) + { + await _houseLogService.Done(input.houseCode.Id); + + var org = await _userManager.GetUserOrgInfo(); + + var _sysEmpRep = Db.GetRepository(); + var _sysUserRoleRep = Db.GetRepository(); + var _sysRoleRep = Db.GetRepository(); + var zoneManagerList = await (from u in _sysUserRep.DetachedEntities + join e in _sysEmpRep.DetachedEntities on u.Id equals e.Id + join ur in _sysUserRoleRep.DetachedEntities on u.Id equals ur.SysUserId + join r in _sysRoleRep.DetachedEntities on ur.SysRoleId equals r.Id + where e.OrgId == org.Id && r.Code == Enum.GetName(HouseManagerRole.ZoneManager).ToUnderScoreCase() + select u).ToListAsync(); + + logSort = await _houseLogService.Add(input.houseCode.Id, zoneManagerList, HouseLogType.Check); + } + //审核操作则新增一条审核记录 + if (dataStatus == DataStatus.Back || dataStatus == DataStatus.Passed) + { + var checkRecord = input.TaskCheckRecord.Adapt(); + await checkRecord.InsertAsync(); + await _houseLogService.Done(input.houseCode.Id); + logSort = await _houseLogService.AddThenDone(input.houseCode.Id, _userManager.User, dataStatus == DataStatus.Passed ? HouseLogType.Agree : HouseLogType.Disagree, remark: checkRecord.Content, sort: logSort); + if (dataStatus == DataStatus.Back) + { + var user = await _sysUserRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id.Equals(houseTask.UserID)); + logSort = await _houseLogService.Add(input.houseCode.Id, user, HouseLogType.CreateInfo, sort: logSort); + } + } + } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseInfo/IHouseInfoService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseInfo/IHouseInfoService.cs new file mode 100644 index 0000000..14dda5f --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseInfo/IHouseInfoService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public interface IHouseInfoService + { + Task GetByTaskId(string taskId); + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseLog/Dto/HouseLogInput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseLog/Dto/HouseLogInput.cs new file mode 100644 index 0000000..356d221 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseLog/Dto/HouseLogInput.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseLogInput + { + [Required(ErrorMessage = "Id不能为空")] + public string Id { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseLog/Dto/HouseLogOutput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseLog/Dto/HouseLogOutput.cs new file mode 100644 index 0000000..fdd2831 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseLog/Dto/HouseLogOutput.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseLogOutput + { + public string Id { get; set; } + public string TargetUserNames { get; set; } + public int Type { get; set; } + public int Status { get; set; } + public string Remark { get; set; } + public DateTime? FinishedTime { get; set; } + public string FinishedUserId { get; set; } + public string FinishedUserName { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseLog/HouseLogService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseLog/HouseLogService.cs new file mode 100644 index 0000000..85cf06f --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseLog/HouseLogService.cs @@ -0,0 +1,209 @@ +using Dapper; +using Ewide.Core; +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + /// + /// 房屋流转日志 + /// + [ApiDescriptionSettings(Name = "HouseLog", Order = 210)] + public class HouseLogService : IHouseLogService, IDynamicApiController, ITransient + { + private readonly IDapperRepository _dapperRep; + + private readonly IUserManager _userManager; + + private readonly IRepository _bsHouseLogRep; + + public HouseLogService(IDapperRepository dapperRep, IUserManager userManager, IRepository bsHouseLogRep) + { + _dapperRep = dapperRep; + _userManager = userManager; + _bsHouseLogRep = bsHouseLogRep; + } + + [NonAction] + [UnitOfWork] + public async Task Add(string houseCodeId, SysUser targetUser, HouseLogType type, string remark = null, int? sort = null) + { + return await Add(houseCodeId, new List { targetUser }, type, remark, sort); + } + + [NonAction] + [UnitOfWork] + public async Task Add(string houseCodeId, List targetUsers, HouseLogType type, string remark = null, int? sort = null) + { + if (!sort.HasValue) + { + sort = await _bsHouseLogRep.DetachedEntities.Where(p => p.HouseCodeId.Equals(houseCodeId)).MaxAsync(p => p.Sort); + } + var _sort = sort.GetValueOrDefault(1) + 1; + await new BsHouseLog + { + Id = Guid.NewGuid().ToString(), + HouseCodeId = houseCodeId, + TargetUserIds = String.Join(",", targetUsers.Select(p => p.Id)), + Type = type, + Status = HouseLogStatus.Handle, + Remark = remark, + Sort = _sort + }.InsertAsync(); + + return _sort; + } + + [NonAction] + [UnitOfWork] + public async Task Read(string houseCodeId) + { + var log = await _bsHouseLogRep + .Where(p => p.HouseCodeId.Equals(houseCodeId) && p.Status.Equals(HouseLogStatus.Handle)) + .OrderByDescending(p => p.CreatedTime) + .FirstOrDefaultAsync(); + if (log != null) + { + log.Status = HouseLogStatus.Handling; + await log.UpdateAsync(); + } + } + + [NonAction] + [UnitOfWork] + public async Task Done(string houseCodeId) + { + var log = await _bsHouseLogRep + .Where(p => p.HouseCodeId.Equals(houseCodeId) && !p.Status.Equals(HouseLogStatus.Handled)) + .OrderByDescending(p => p.CreatedTime) + .FirstOrDefaultAsync(); + if (log != null) + { + log.Status = HouseLogStatus.Handled; + log.FinishedTime = DateTime.Now; + log.FinishedUserId = _userManager.UserId; + log.FinishedUserName = _userManager.User.Account; + await log.UpdateAsync(); + } + } + + [NonAction] + [UnitOfWork] + public async Task AddThenRead(string houseCodeId, SysUser targetUser, HouseLogType type, string remark = null, int? sort = null) + { + return await AddThenRead(houseCodeId, new List { targetUser }, type, remark, sort); + } + + [NonAction] + [UnitOfWork] + public async Task AddThenRead(string houseCodeId, List targetUsers, HouseLogType type, string remark = null, int? sort = null) + { + if (!sort.HasValue) + { + sort = await _bsHouseLogRep.DetachedEntities.Where(p => p.HouseCodeId.Equals(houseCodeId)).MaxAsync(p => p.Sort); + } + var _sort = sort.GetValueOrDefault(1) + 1; + await new BsHouseLog + { + Id = Guid.NewGuid().ToString(), + HouseCodeId = houseCodeId, + TargetUserIds = String.Join(",", targetUsers.Select(p => p.Id)), + Type = type, + Status = HouseLogStatus.Handling, + Remark = remark, + Sort = _sort + }.InsertAsync(); + + return _sort; + } + + [NonAction] + [UnitOfWork] + public async Task AddThenDone(string houseCodeId, SysUser targetUser, HouseLogType type, string remark = null, int? sort = null) + { + return await AddThenDone(houseCodeId, new List { targetUser }, type, remark, sort); + } + + [NonAction] + [UnitOfWork] + public async Task AddThenDone(string houseCodeId, List targetUsers, HouseLogType type, string remark = null, int? sort = null) + { + if (!sort.HasValue) + { + sort = await _bsHouseLogRep.DetachedEntities.Where(p => p.HouseCodeId.Equals(houseCodeId)).MaxAsync(p => p.Sort); + } + var _sort = sort.GetValueOrDefault(1) + 1; + await new BsHouseLog + { + Id = Guid.NewGuid().ToString(), + HouseCodeId = houseCodeId, + TargetUserIds = String.Join(",", targetUsers.Select(p => p.Id)), + Type = type, + Status = HouseLogStatus.Handled, + Remark = remark, + FinishedTime = DateTime.Now, + FinishedUserId = _userManager.UserId, + FinishedUserName = _userManager.User.Account, + Sort = _sort + }.InsertAsync(); + + return _sort; + } + + [HttpGet("/houseLog/list")] + public async Task List([FromQuery] HouseLogInput input) + { + var sql = @"SELECT + HL.Id, + HL.Type, + HL.`Status`, + HL.Remark, + HL.FinishedTime, + HL.FinishedUserId, + IFNULL(NickName, `Name`) FinishedUserName, +(SELECT GROUP_CONCAT(IFNULL(NickName,`Name`)) FROM sys_user + WHERE Id IN ( + SELECT DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(_HL.TargetUserIds,',',HT.help_topic_id + 1),',',-1) + FROM bs_house_log _HL + JOIN mysql.help_topic HT ON HT.help_topic_id < (LENGTH(_HL.TargetUserIds) - LENGTH(REPLACE(_HL.TargetUserIds,',','')) + 1) + WHERE _HL.Id = HL.Id + ) +) TargetUserNames +FROM bs_house_log HL +LEFT JOIN sys_user SU ON HL.FinishedUserId = SU.Id +WHERE HouseCodeId = @HouseCodeId +ORDER BY Sort DESC"; + + return await _dapperRep.QueryAsync(sql, new { houseCodeId = input.Id }); + } + + [HttpGet("/houseLog/listByInfoId")] + public async Task ListByInfoId([FromQuery] HouseLogInput input) + { + var info = await Db.GetRepository().DetachedEntities.FirstOrDefaultAsync(p => p.Id.Equals(input.Id)); + return await List(new HouseLogInput + { + Id = info.HouseCodeId + }); + } + + [HttpGet("/houseLog/listByTaskId")] + public async Task ListByTaskId([FromQuery] HouseLogInput input) + { + var task = await Db.GetRepository().DetachedEntities.FirstOrDefaultAsync(p => p.Id.Equals(input.Id)); + return await List(new HouseLogInput + { + Id = task.HouseCodeId + }); + } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseLog/IHouseLogService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseLog/IHouseLogService.cs new file mode 100644 index 0000000..056db2b --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseLog/IHouseLogService.cs @@ -0,0 +1,24 @@ +using Ewide.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public interface IHouseLogService + { + Task Add(string houseCodeId, SysUser targetUser, HouseLogType type, string remark = null, int? sort = null); + Task Add(string houseCodeId, List targetUsers, HouseLogType type, string remark = null, int? sort = null); + Task Read(string houseCodeId); + Task Done(string houseCodeId); + Task AddThenRead(string houseCodeId, SysUser targetUser, HouseLogType type, string remark = null, int? sort = null); + Task AddThenRead(string houseCodeId, List targetUsers, HouseLogType type, string remark = null, int? sort = null); + Task AddThenDone(string houseCodeId, SysUser targetUser, HouseLogType type, string remark = null, int? sort = null); + Task AddThenDone(string houseCodeId, List targetUsers, HouseLogType type, string remark = null, int? sort = null); + Task List(HouseLogInput input); + Task ListByInfoId(HouseLogInput input); + Task ListByTaskId(HouseLogInput input); + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseMember/Dto/HouseMemberInput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseMember/Dto/HouseMemberInput.cs new file mode 100644 index 0000000..e3634ea --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseMember/Dto/HouseMemberInput.cs @@ -0,0 +1,16 @@ +using Ewide.Core.Service; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public class AddHouseMemberInput : AddUserInput + { + [Required(ErrorMessage = "角色不能为空")] + public string RoleId { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseMember/Dto/HouseMemberOutput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseMember/Dto/HouseMemberOutput.cs new file mode 100644 index 0000000..73d2395 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseMember/Dto/HouseMemberOutput.cs @@ -0,0 +1,13 @@ +using Ewide.Core.Service; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public class HouseMemberOutput + { + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseMember/HouseMemberService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseMember/HouseMemberService.cs new file mode 100644 index 0000000..a77a0b5 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseMember/HouseMemberService.cs @@ -0,0 +1,326 @@ +using Dapper; +using Ewide.Core; +using Ewide.Core.Extension; +using Ewide.Core.Service; +using Furion.DatabaseAccessor; +using Furion.DataEncryption; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System; + +namespace Ewide.Application.Service +{ + [ApiDescriptionSettings(Name = "HouseMember")] + public class HouseMemberService : IHouseMemberService, IDynamicApiController, ITransient + { + private readonly IDapperRepository _dapperRepository; + + private readonly IRepository _sysUserRep; // 用户表仓储 + private readonly IUserManager _userManager; + private readonly ISysUserService _sysUserService; + private readonly ISysEmpService _sysEmpService; + + public HouseMemberService( + IDapperRepository dapperRepository, + IRepository sysUserRep, + IUserManager userManager, + ISysUserService sysUserService, + ISysEmpService sysEmpService + ) + { + _dapperRepository = dapperRepository; + _sysUserRep = sysUserRep; + _userManager = userManager; + _sysUserService = sysUserService; + _sysEmpService = sysEmpService; + } + + /// + /// 分页查询用户 + /// + /// + /// + [HttpPost("/houseMember/page")] + public async Task QueryMemberPageList([FromBody] UserInput input) + { + var dataScopes = await _userManager.GetUserAllDataScopeList(); + + var sql = @"SELECT +SU.*, +SO.Name OrgName, +SUR.RoleName, +SUR.RoleCode +FROM sys_user SU +LEFT JOIN sys_emp SE ON SU.Id = SE.Id +LEFT JOIN sys_org SO ON SE.OrgId = SO.Id +LEFT JOIN ( + SELECT + SUR.SysUserId, + GROUP_CONCAT(SR.Name) RoleName, + GROUP_CONCAT(SR.Code) RoleCode + FROM sys_user_role SUR + LEFT JOIN sys_role SR ON SUR.SysRoleId = SR.Id + GROUP BY SUR.SysUserId +) SUR ON SU.Id = SUR.SysUserId +WHERE 1=1"; + var param = new DynamicParameters(); + + if (!string.IsNullOrEmpty(input.SearchValue)) + { + sql += @" AND (SU.Account Like @SearchValue + OR SU.Name Like @SearchValue + OR SU.Phone Like @SearchValue)"; + param.Add("SearchValue", "%" + input.SearchValue + "%"); + } + + if (!string.IsNullOrEmpty(input.SysEmpParam.OrgId)) + { + var filter = " AND (SO.Id = @OrgId {0}) "; + filter = String.Format(filter, input.TreeNodeDataScope.GetValueOrDefault(1) == 2 ? " OR SO.Pids Like CONCAT('%[', @OrgId, ']%') " : ""); + sql += filter; + param.Add("OrgId", input.SysEmpParam.OrgId); + } + + if (input.SearchStatus >= 0) + { + sql += " AND SU.Status = @Status"; + param.Add("Status", input.SearchStatus); + } + + if (!_userManager.SuperAdmin) + { + sql += " AND SU.AdminType <> @AdminType"; + param.Add("AdminType", AdminType.SuperAdmin); + + sql += " AND SU.Id <> @UserId"; + param.Add("UserId", _userManager.UserId); + + if (dataScopes.Count > 0) + { + sql += " AND SO.Id IN @OrgIds"; + param.Add("OrgIds", dataScopes.ToArray()); + } + } + + var users = await _dapperRepository.QueryPageData(sql, input, param); + + //foreach (var user in users.Items) + //{ + // user.SysEmpInfo = await _sysEmpService.GetEmpInfo(user.Id); + //} + return PageDataResult.PageResult(users); + } + + /// + /// 增加用户 + /// + /// + /// + [HttpPost("/houseMember/add")] + [UnitOfWork] + public async Task AddUser(AddHouseMemberInput input) + { + var id = await _sysUserService.AddUser(input); + var grantRoleInput = input.Adapt(); + grantRoleInput.Id = id; + + // 添加角色 + grantRoleInput.GrantRoleIdList.Add(input.RoleId); + await _sysUserService.GrantUserRole(grantRoleInput); + } + + /// + /// 获取角色 + /// + /// + /// + [HttpGet("/houseMember/defaultRole")] + public async Task DefaultRole([FromQuery] string orgId) + { + return (await GetDefaultRole(orgId)).Adapt(); + } + + /// + /// 获取用户拥有角色 + /// + /// + /// + [HttpGet("/houseMember/ownRole")] + public async Task GetUserOwnRole([FromQuery] QueryUserInput input) + { + return await _sysUserService.GetUserOwnRole(input); + } + + [HttpGet("/houseMember/defaultRoleRange")] + public async Task DefaultRoleRange() + { + return (await GetRoleRange()).Select(p => p.Adapt()).ToList(); + } + + /// + /// 删除用户 + /// + /// + /// + [HttpPost("/houseMember/delete")] + [UnitOfWork] + public async Task DeleteUser(DeleteUserInput input) + { + /* + * 未处理逻辑 + * 片区监管员在本片区已有安全员的情况下无法删除 + */ + + await _sysUserService.DeleteUser(input); + } + + /// + /// 更新用户 + /// + /// + /// + [HttpPost("/houseMember/edit")] + [UnitOfWork] + public async Task UpdateUser(UpdateUserInput input) + { + /* + * 未处理逻辑 + * 如果移动组织架构,会产生以下几种逻辑 + * 片区1监管员(无安全员或有其他监管员)=>片区2,直接成功 + * 片区1监管员(有安全员并且无其他监管员)=>片区2,无法移动 + * 片区1安全员=>片区2(有监管员),直接成功 + * 片区1安全员=>片区2(无监管员),无法移动,需要创建监管员 + */ + + await _sysUserService.UpdateUser(input); + } + + /// + /// 查看用户 + /// + /// + /// + [HttpGet("/houseMember/detail")] + public async Task GetUser([FromQuery] QueryUserInput input) + { + var sql = @"SELECT +SU.*, +SO.Name OrgName, +SUR.RoleName, +SUR.RoleCode +FROM sys_user SU +LEFT JOIN sys_emp SE ON SU.Id = SE.Id +LEFT JOIN sys_org SO ON SE.OrgId = SO.Id +LEFT JOIN ( + SELECT + SUR.SysUserId, + GROUP_CONCAT(SR.Name) RoleName, + GROUP_CONCAT(SR.Code) RoleCode + FROM sys_user_role SUR + LEFT JOIN sys_role SR ON SUR.SysRoleId = SR.Id + GROUP BY SUR.SysUserId +) SUR ON SU.Id = SUR.SysUserId +WHERE SU.Id=@Id"; + var user = (await _dapperRepository.QueryAsync(sql, new { input.Id })).SingleOrDefault(); + if (user != null) + { + user.SysEmpInfo = await _sysEmpService.GetEmpInfo(user.Id); + } + return user; + } + + /// + /// 修改用户状态 + /// + /// + /// + [HttpPost("/houseMember/changeStatus")] + public async Task ChangeUserStatus(UpdateUserInput input) + { + await _sysUserService.ChangeUserStatus(input); + } + + /// + /// 授权用户数据范围 + /// + /// + /// + [HttpPost("/houseMember/grantData")] + public async Task GrantUserData(UpdateUserInput input) + { + await _sysUserService.GrantUserData(input); + } + + /// + /// 获取用户拥有数据 + /// + /// + /// + [HttpGet("/houseMember/ownData")] + public async Task GetUserOwnData([FromQuery] QueryUserInput input) + { + return await _sysUserService.GetUserOwnData(input); + } + + /// + /// 获取当前机构中所创建的默认角色 + /// 片区中的第一个帐号必定为片区监管员 + /// + /// + /// + [NonAction] + private async Task GetDefaultRole(string orgId) + { + var roles = await GetRoleRange(); + if (roles.Count < 2) throw Oops.Oh("未配置正确的角色"); + + var _sysOrgRep = Db.GetRepository(); + var org = await _sysOrgRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == orgId); + // 如果当前组织为街道, 则直接返回安全员 + if (org == null || org.Type <= (int)OrgType.乡镇街道办事处) + { + return roles.LastOrDefault(); + } + + var _sysEmpRep = Db.GetRepository(); + var _sysUserRoleRep = Db.GetRepository(); + var userIds = await _sysEmpRep.DetachedEntities.Where(p => p.OrgId == orgId).Select(p => p.Id).ToListAsync(); + var roleIds = await _sysUserRoleRep.DetachedEntities.Where(p => userIds.Contains(p.SysUserId)).Select(p => p.SysRoleId).ToListAsync(); + + var _sysRoleRep = Db.GetRepository(); + var isExistZoneManager = await _sysRoleRep.DetachedEntities.AnyAsync(p => roleIds.Contains(p.Id) && p.Code == Enum.GetName(HouseManagerRole.ZoneManager).ToUnderScoreCase()); + // 存在片区监管员,返回安全员, 否则返回监管员 + if (isExistZoneManager) + { + return roles.LastOrDefault(); + } + + return roles.FirstOrDefault(); + } + + /// + /// 获取可创建的角色列表 + /// + /// + [NonAction] + private async Task> GetRoleRange() + { + var codes = (new[] { + Enum.GetName(HouseManagerRole.ZoneManager), + Enum.GetName(HouseManagerRole.HouseSecurityManager), + }).Select(p => p.ToUnderScoreCase()); + + var _sysRoleRep = Db.GetRepository(); + var roles = await _sysRoleRep.DetachedEntities.Where(p => codes.Contains(p.Code)).ToListAsync(); + + return roles; + } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseMember/IHouseMemberService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseMember/IHouseMemberService.cs new file mode 100644 index 0000000..76ea0f5 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseMember/IHouseMemberService.cs @@ -0,0 +1,22 @@ +using Ewide.Core.Service; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public interface IHouseMemberService + { + Task QueryMemberPageList([FromBody] UserInput input); + Task AddUser(AddHouseMemberInput input); + Task DefaultRole([FromQuery] string orgId); + Task DeleteUser(DeleteUserInput input); + Task UpdateUser(UpdateUserInput input); + Task GetUser([FromQuery] QueryUserInput input); + Task ChangeUserStatus(UpdateUserInput input); + Task GetUserOwnRole([FromQuery] QueryUserInput input); + Task GrantUserData(UpdateUserInput input); + Task GetUserOwnData([FromQuery] QueryUserInput input); + Task DefaultRoleRange(); + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/Dto/HouseProjectInfoInput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/Dto/HouseProjectInfoInput.cs new file mode 100644 index 0000000..b2e4e17 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/Dto/HouseProjectInfoInput.cs @@ -0,0 +1,52 @@ +using Ewide.Core; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service.HouseProjectInfo.Dto +{ + public class HouseProjectInfoInput : InputBase + { + public string Name { get; set; } + public string Note { get; set; } + public int Sort { get; set; } + public string AreaCode { get; set; } + public int Type { get; set; } + } + public class AddProjectInput : HouseProjectInfoInput + { + + } + public class UpdateProjectInput : HouseProjectInfoInput + { + [Required(ErrorMessage = "项目ID不可为空")] + public string Id { get; set; } + } + + public class DeleteProjectInput + { + [Required(ErrorMessage = "项目ID不可为空")] + public string Id { get; set; } + } + + public class QueryProjectInput : UpdateProjectInput + { + + } + + public class PageProjectInput: HouseProjectInfoInput + { + public string pid { get; set; } + } + + public class ListHouseProjectInfoInput + { + [Required(ErrorMessage = "区域编码不可为空")] + public string AreaCode { get; set; } + [Required(ErrorMessage = "性质不可为空")] + public int Type { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/Dto/HouseProjectOutput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/Dto/HouseProjectOutput.cs new file mode 100644 index 0000000..307b548 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/Dto/HouseProjectOutput.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service.HouseProjectInfo.Dto +{ + public class HouseProjectOutput + { + public string Id { get; set; } + public string Name { get; set; } + public string Note { get; set; } + public int Sort { get; set; } + public string AreaCode { get; set; } + public string AreaName { get; set; } + public int Type { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/HouseProjectInfoService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/HouseProjectInfoService.cs new file mode 100644 index 0000000..d0f8112 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/HouseProjectInfoService.cs @@ -0,0 +1,147 @@ +using Ewide.Application.Entity; +using Ewide.Application.Service.HouseProjectInfo.Dto; +using Ewide.Core; +using Ewide.Core.Service; +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Data; +using System.Linq; +using System.Threading.Tasks; + +namespace Ewide.Application.Service.HouseProjectInfo +{ + /// + /// 项目管理相关服务 + /// + [ApiDescriptionSettings(Name = "HouseProjectInfo", Order = 170)] + public class HouseProjectInfoService : IHouseProjectInfoService, ITransient, IDynamicApiController + { + private readonly IRepository _houseProjectInfoRep; + + public HouseProjectInfoService(IRepository houseProjectInfoRep) + { + _houseProjectInfoRep = houseProjectInfoRep; + } + + /// + /// 添加项目 + /// + /// + /// + [HttpPost("/houseProjectInfo/add")] + public async Task AddProject(AddProjectInput input) + { + await _houseProjectInfoRep.InsertNowAsync(input.Adapt()); + } + + /// + /// 删除项目 + /// + /// + /// + [HttpPost("/houseProjectInfo/delete")] + public async Task DeleteProject(DeleteProjectInput input) + { + var project = _houseProjectInfoRep.FirstOrDefault(p => p.Id == input.Id); + await project.DeleteNowAsync(); + } + + /// + /// 编辑项目 + /// + /// + /// + [HttpPost("/houseProjectInfo/edit")] + public async Task UpdateProject(UpdateProjectInput input) + { + var project = input.Adapt(); + await project.UpdateExcludeAsync(new[] { nameof(BsHouseProjectInfo.AreaCode) }, true); + } + + /// + /// 通过ID获取项目 + /// + /// + /// + [HttpGet("/houseProjectInfo/getById")] + public async Task GetById([Required] string projectId) + { + return await _houseProjectInfoRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == projectId); + } + + /// + /// 分页查询 + /// + /// + /// + [HttpPost("/houseProjectInfo/page")] + public async Task QueryProjectPageList([FromBody] PageProjectInput input) + { + var areaCodeRep = Db.GetRepository(); + var projects = await _houseProjectInfoRep.DetachedEntities + .Join(areaCodeRep.DetachedEntities, p => p.AreaCode, a => a.Code, (p, a) => new { p, AreaName = a.Name }) + .Where(input.Type>0, x => x.p.Type == input.Type) + .Where(!string.IsNullOrEmpty(input.Name), x => x.p.Name.Contains(input.Name)) + .Where(!string.IsNullOrEmpty(input.Note), x => x.p.Note.Contains(input.Note)) + .Where(!string.IsNullOrEmpty(input.AreaCode), x => x.p.AreaCode == input.AreaCode) + .Where(!string.IsNullOrEmpty(input.pid) , x=> x.p.AreaCode.Contains(input.pid)) + .Select(x => new { x.p.Id, x.p.Name, x.p.Note, x.p.Sort, x.p.AreaCode, x.AreaName, x.p.Type }.Adapt()).ToPagedListAsync(input.PageIndex, input.PageSize); + return PageDataResult.PageResult(projects); + } + + /// + /// + /// + /// + /// + [HttpGet("/houseProjectInfo/detail")] + public async Task GetProject([FromQuery] QueryProjectInput input) + { + var user = await _houseProjectInfoRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == input.Id); + var userDto = user.Adapt(); + + return userDto; + } + + [HttpGet("/houseProjectInfo/nextSort")] + public async Task GetNextProjectSortByAreaCode([FromQuery] ListHouseProjectInfoInput input) + { + //var projects = await _houseProjectInfoRep.DetachedEntities + // .Where(p => p.AreaCode == input.AreaCode && p.Type == input.Type) + // .Select(p => p.Sort) + // .DefaultIfEmpty() + // .MaxAsync(); + if (input.Type > 2 || input.Type < 1) + { + throw Oops.Oh("类型参数异常"); + } + var p = await _houseProjectInfoRep.DetachedEntities + .Where(p => p.AreaCode == input.AreaCode && p.Type == input.Type) + .MaxAsync(p => (int?)p.Sort); + + return p.GetValueOrDefault(0) + 1; + } + + /// + /// 获取项目下拉列表 + /// + /// + /// + [HttpGet("houseProjectInfo/list")] + public async Task GetProjectList([FromQuery] ListHouseProjectInfoInput input) + { + return await _houseProjectInfoRep.DetachedEntities + .Where(p => p.AreaCode == input.AreaCode && p.Type == input.Type) + .OrderBy(p => p.Sort) + .ToListAsync(); + } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/IHouseProjectInfoService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/IHouseProjectInfoService.cs new file mode 100644 index 0000000..e8b05c6 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/IHouseProjectInfoService.cs @@ -0,0 +1,24 @@ +using Ewide.Application.Entity; +using Ewide.Application.Service.HouseProjectInfo.Dto; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service.HouseProjectInfo +{ + public interface IHouseProjectInfoService + { + Task AddProject(AddProjectInput input); + Task DeleteProject(DeleteProjectInput input); + Task UpdateProject(UpdateProjectInput input); + Task GetById([FromRoute] string projectId); + Task GetProject([FromQuery] QueryProjectInput input); + Task QueryProjectPageList([FromQuery] PageProjectInput input); + + Task GetNextProjectSortByAreaCode([FromQuery] ListHouseProjectInfoInput input); + Task GetProjectList([FromQuery] ListHouseProjectInfoInput input); + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseQuery/Dto/HouseQueryInput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseQuery/Dto/HouseQueryInput.cs new file mode 100644 index 0000000..096f601 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseQuery/Dto/HouseQueryInput.cs @@ -0,0 +1,21 @@ +using Ewide.Core; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseQueryInput: PageInputBase + { + + } + + public class HouseQueryDetailInput + { + [Required(ErrorMessage = "Id不能为空")] + public string Id { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseQuery/Dto/HouseQueryOutput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseQuery/Dto/HouseQueryOutput.cs new file mode 100644 index 0000000..a4464e1 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseQuery/Dto/HouseQueryOutput.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseQueryOutput + { + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseQuery/HouseQueryService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseQuery/HouseQueryService.cs new file mode 100644 index 0000000..5800149 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseQuery/HouseQueryService.cs @@ -0,0 +1,110 @@ +using Dapper; +using Ewide.Core.Extension; +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service.HouseSafety.HouseQuery +{ + /// + /// 住宅查询 + /// + [ApiDescriptionSettings(Name = "HouseQuery", Order = 210)] + public class HouseQueryService : IHouseQueryService, IDynamicApiController, ITransient + { + private readonly IRepository _houseCodeRep; + private readonly IRepository _houseInfoRep; + private readonly IRepository _houseTaskRep; + private readonly IDapperRepository _dapperRepository; + + public HouseQueryService(IRepository HouseCodeRep, IRepository HouseInfoRep, IRepository HouseTaskRep, IDapperRepository dapperRepository) + { + _houseCodeRep = HouseCodeRep; + _houseInfoRep = HouseInfoRep; + _houseTaskRep = HouseTaskRep; + _dapperRepository = dapperRepository; + } + + [HttpPost("/houseQuery/page")] + public async Task QueryPage([FromBody] HouseQueryInput input) + { + var sql = @"SELECT + HC.ID, + HC.HouseCode, + AA.Name AreaName, + RA.Name RoadName, + CA.Name CommName, + Proj.AreaCode, + Proj.Note, + Proj.Name, + CONCAT(Proj.Name,'(',Proj.Note,')') FullProjName, + HC.Address, + IFNULL(HI.BuildingName,'') BuildingName, + IFNULL(HI.TotalFloor,0) TotalFloor, + IFNULL(HI.TotalArea,0) TotalArea, + HI.LandAttribute, + IFNULL(HI.HouseGrade,0) HouseGrade, + HC.Type, + HC.No, + HI.State, + HI.CompletedDate, + HI.CreatedTime +FROM bs_house_code HC +LEFT JOIN bs_house_info HI ON HI.HouseCodeId = HC.Id +LEFT JOIN bs_house_projectinfo Proj ON Proj.Id=HC.ProjectId +LEFT JOIN sys_area_code CA ON CA.Code = Proj.AreaCode +LEFT JOIN sys_area_code RA ON RA.AdCode = SUBSTR(CA.AdCode,1,9) +LEFT JOIN sys_area_code AA ON AA.AdCode = SUBSTR(CA.AdCode,1,6) +WHERE 1=1"; + + return await _dapperRepository.QueryPageDataDynamic(sql, input, filterFields: new string[] { + "HouseCode", + "Address", + "BuildingName", + "State", + "AreaCode", + "LandAttribute", + "HouseGrade", + "CompletedDate", + "CreatedTime", + "TotalArea", + "TotalFloor" + }); + } + + [HttpGet("/houseQuery/detail")] + public async Task Detail([FromQuery] HouseQueryDetailInput input) + { + var houseCodeOutputAsync = await _dapperRepository.QueryAsync( + @"SELECT HC.Id,HC.Address,HC.ProjectId,HC.ZoneId,HC.Type,AA.Name AreaName,RA.Name RoadName,CA.Name CommName,O.Name ZoneName,Proj.Note ProjectNote,CONCAT(Proj.Name,'(',Proj.Note,')') ProjectFullName,HC.HouseCode,HC.Lng,HC.Lat,HC.No FROM bs_house_code HC +LEFT JOIN bs_house_projectinfo Proj ON Proj.Id=HC.ProjectId +LEFT JOIN sys_org O ON HC.ZoneId = O.Id +LEFT JOIN sys_area_code CA ON CA.Code = Proj.AreaCode +LEFT JOIN sys_area_code RA ON RA.AdCode = SUBSTR(CA.AdCode,1,9) +LEFT JOIN sys_area_code AA ON AA.AdCode = SUBSTR(CA.AdCode,1,6) +WHERE HC.Id=@Id", new { input.Id } + ); + + var houseCodeOutput = houseCodeOutputAsync.SingleOrDefault(); + + + var houseInfoOutputForDetailPage = new HouseInfoOutputForDetailPage + { + HouseCode = houseCodeOutput + }; + + var houseInfoOutput = (await _houseInfoRep.DetachedEntities.FirstOrDefaultAsync(p => p.HouseCodeId == houseCodeOutput.Id)).Adapt(); + houseInfoOutputForDetailPage.HouseInfo = houseInfoOutput; + + return houseInfoOutputForDetailPage; + } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseQuery/IHouseQueryService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseQuery/IHouseQueryService.cs new file mode 100644 index 0000000..aa1e217 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseQuery/IHouseQueryService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service.HouseSafety.HouseQuery +{ + public interface IHouseQueryService + { + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseSelector/Dto/HouseSelectorInput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseSelector/Dto/HouseSelectorInput.cs new file mode 100644 index 0000000..0001eff --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseSelector/Dto/HouseSelectorInput.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseSelectInput + { + [Required(ErrorMessage = "用户Id不能为空")] + public string UserId { get; set; } + + [Required(ErrorMessage = "房屋编码Id不能为空")] + public string[] Ids { get; set; } + } + + public class QueryHouseSelectorInput : QueryHouseCodeInput + { + [Required(ErrorMessage = "用户Id不能为空")] + public string UserId { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseSelector/Dto/HouseSelectorOutput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseSelector/Dto/HouseSelectorOutput.cs new file mode 100644 index 0000000..43fd2e9 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseSelector/Dto/HouseSelectorOutput.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseSelectorOutput + { + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseSelector/HouseSelectorService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseSelector/HouseSelectorService.cs new file mode 100644 index 0000000..df04bd2 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseSelector/HouseSelectorService.cs @@ -0,0 +1,225 @@ +using Dapper; +using Ewide.Core; +using Ewide.Core.Extension; +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + /// + /// 选房相关 + /// + [ApiDescriptionSettings(Name = "HouseSelector", Order = 180)] + public class HouseSelectorService : IHouseSelectorService, IDynamicApiController, ITransient + { + private readonly IUserManager _userManager; + private readonly IDapperRepository _dapperRep; + + private readonly IRepository _bsHouseMemberRelationRep; + private readonly IRepository _bsHouseCodeRep; + private readonly IRepository _bsHouseTaskRep; + private readonly IRepository _sysUserRep; + private readonly IHouseZoneService _houseZoneService; + + private readonly IHouseLogService _houseLogService; + + public HouseSelectorService( + IUserManager userManager, + IDapperRepository dapperRep, + IRepository bsHouseMemberRelationRep, + IRepository bsHouseCodeRep, + IRepository bsHouseTaskRep, + IRepository sysUserRep, + IHouseZoneService houseZoneService, + IHouseLogService houseLogService + ) + { + _userManager = userManager; + + _dapperRep = dapperRep; + _bsHouseMemberRelationRep = bsHouseMemberRelationRep; + _bsHouseCodeRep = bsHouseCodeRep; + _bsHouseTaskRep = bsHouseTaskRep; + _sysUserRep = sysUserRep; + _houseZoneService = houseZoneService; + + _houseLogService = houseLogService; + } + + /// + /// 获取人员允许绑定的房屋编码列表 + /// + /// + /// + [HttpPost("/houseSelector/selectorPage")] + public async Task HouseSelectorList([FromBody] QueryHouseSelectorInput input) + { + var sql = @"SELECT + HC.*,AA.Name AreaName,RA.Name RoadName,CA.Name CommName,Proj.AreaCode,Proj.Note,CONCAT(Proj.`Name`, IFNULL(CONCAT('(',Proj.Note,')'), '')) FullProjName +FROM bs_house_code HC +LEFT JOIN bs_house_projectinfo Proj ON Proj.Id=HC.ProjectId +LEFT JOIN sys_area_code CA ON CA.Code = Proj.AreaCode +LEFT JOIN sys_area_code RA ON RA.AdCode = SUBSTR(CA.AdCode,1,9) +LEFT JOIN sys_area_code AA ON AA.AdCode = SUBSTR(CA.AdCode,1,6) +LEFT JOIN bs_house_member_relation HM ON HC.Id = HM.HouseCodeId +INNER JOIN (SELECT * FROM sys_emp WHERE Id = @UserId) E ON HC.ZoneId = E.OrgId +WHERE HM.Id IS NULL"; + return await _dapperRep.QueryPageDataDynamic(sql, input, param: new + { + input.UserId + }, filterFields: new[] { + nameof(BsHouseCode.Address) , + nameof(BsHouseCode.CreatedTime) + }); + } + + /// + /// 获取人员已经绑定的房屋编码列表 + /// + /// + /// + [HttpPost("/houseSelector/selectedPage")] + public async Task HouseSelectedList([FromBody] QueryHouseSelectorInput input) + { + var sql = @"SELECT + HC.*,AA.Name AreaName,RA.Name RoadName,CA.Name CommName,Proj.AreaCode,Proj.Note,CONCAT(Proj.`Name`, IFNULL(CONCAT('(',Proj.Note,')'), '')) FullProjName +FROM bs_house_code HC +LEFT JOIN bs_house_projectinfo Proj ON Proj.Id=HC.ProjectId +LEFT JOIN sys_area_code CA ON CA.Code = Proj.AreaCode +LEFT JOIN sys_area_code RA ON RA.AdCode = SUBSTR(CA.AdCode,1,9) +LEFT JOIN sys_area_code AA ON AA.AdCode = SUBSTR(CA.AdCode,1,6) +INNER JOIN (SELECT * FROM bs_house_member_relation WHERE SysUserId = @UserId) HM ON HC.Id = HM.HouseCodeId"; + return await _dapperRep.QueryPageDataDynamic(sql, input, param: new + { + input.UserId + }, filterFields: new[] { + nameof(BsHouseCode.Address) , + nameof(BsHouseCode.CreatedTime) + }); + } + + /// + /// 从人员选择房屋 + /// + /// + /// + [HttpPost("/houseSelector/select")] + [UnitOfWork] + public async Task Select([FromBody] HouseSelectInput input) + { + #region 验证房屋是否在当前用户可选范围内 + + var ids = input.Ids.Distinct().ToList(); + if (ids.Count == 0) throw Oops.Oh("没有选中任何房屋"); + + // 验证当前用户是否安全员 **须补充 + + // 已经被选中的房屋,从ids中剔除 + var houseSelected = await _bsHouseMemberRelationRep.DetachedEntities + .Where(p => ids.Contains(p.HouseCodeId)) + .Select(p => p.HouseCodeId) + .ToListAsync(); + if (houseSelected.Count > 0) + { + houseSelected.ForEach(p => + { + var index = ids.IndexOf(p); + if (index > -1) + { + ids.RemoveAt(index); + } + }); + } + + if (ids.Count == 0) throw Oops.Oh("当前房屋在此之前已经全部被选中,请确认"); + + var selectedUser = await _sysUserRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id.Equals(input.UserId)); + + // 从用户所在片区中过滤房屋 + var zoneId = await _houseZoneService.GetZoneByUser(selectedUser.Id); + var house = await _bsHouseCodeRep.DetachedEntities.Where(p => ids.Contains(p.Id) && p.ZoneId == zoneId).Select(p => p.Id).ToListAsync(); + + if (house.Count == 0) throw Oops.Oh("选中的房屋错误"); + + #endregion + + // 选定房屋 + house.ForEach(async p => + { + var originalTask = _bsHouseTaskRep.DetachedEntities.FirstOrDefault(t => t.HouseCodeId == p && t.TaskType == 0 && t.IsEnabled); + if (originalTask == null) + { + new BsHouseTask + { + Id = System.Guid.NewGuid().ToString(), + HouseCodeId = p, + UserID = selectedUser.Id, + EndTime = System.DateTime.Now.AddMonths(1), + Status = 0, + TaskType = 0 + }.Insert(); + + await _houseLogService.Done(p); + await _houseLogService.Add(p, selectedUser, HouseLogType.CreateInfo); + } + else + { + var sort = await _houseLogService.AddThenDone(p, _userManager.User, HouseLogType.SelectMember); + if (originalTask.Status != 6)//建档未完成,生成新建档任务分配给新的人员;原建档任务数据保留,有效性设置为false,取消巡查关系 + { + var newTask = originalTask.Adapt(); + newTask.Id = System.Guid.NewGuid().ToString(); + newTask.UserID = input.UserId; + newTask.EndTime = System.DateTime.Now.AddMonths(1); + newTask.Insert(); + + originalTask.IsEnabled = false; + originalTask.Update(); + + sort = await _houseLogService.Add(p, selectedUser, HouseLogType.CreateInfo, sort: sort); + }//已建档完成,不再分配建档任务,仅更换巡查关系 + else + { + var originalRelation = _bsHouseMemberRelationRep.DetachedEntities.FirstOrDefault(r => r.HouseCodeId == p && r.SysUserId == originalTask.UserID); + originalRelation.Delete(); + } + } + + new BsHouseMemberRelation + { + SysUserId = selectedUser.Id, + HouseCodeId = p + }.Insert(); + }); + } + + [HttpPost("/houseSelector/revoke")] + [UnitOfWork] + public async Task Revoke([FromBody] HouseSelectInput input) + { + var ids = input.Ids.Distinct().ToList(); + if (ids.Count == 0) throw Oops.Oh("没有选中任何房屋"); + + var selected = await _bsHouseMemberRelationRep.Where(p => ids.Contains(p.HouseCodeId) && p.SysUserId == input.UserId).ToListAsync(); + + selected.ForEach(p => + { + p.Delete(); + }); + } + + public async Task SelectMember() + { + + } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseSelector/IHouseSelectorService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseSelector/IHouseSelectorService.cs new file mode 100644 index 0000000..df71ac7 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseSelector/IHouseSelectorService.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public interface IHouseSelectorService + { + Task HouseSelectorList([FromQuery] QueryHouseSelectorInput input); + Task HouseSelectedList([FromQuery] QueryHouseSelectorInput input); + Task Select([FromBody] HouseSelectInput input); + Task Revoke([FromBody] HouseSelectInput input); + Task SelectMember(); + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseTask/Dto/HouseTaskInput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseTask/Dto/HouseTaskInput.cs new file mode 100644 index 0000000..53bdf4b --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseTask/Dto/HouseTaskInput.cs @@ -0,0 +1,171 @@ +using Ewide.Core; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseTaskInput + { + [Required(ErrorMessage = "巡查日期不能为空")] + public DateTime PatrolDate { get; set; } + + [Required(ErrorMessage = "巡查人员/单位不能为空")] + public string PatrolUser { get; set; } + + /// + /// 沉降倾斜 + /// + public string SettlementTilt { get; set; } + + /// + /// 沉降倾斜附件 + /// + public string SettlementTiltFiles { get; set; } + + /// + /// 其他情况 + /// + public string OtherInfo { get; set; } + + /// + /// 其他情况附件 + /// + public string OtherInfoFiles { get; set; } + + //[Required(ErrorMessage = "巡查初始等级不能为空")] + public int InitGrade { get; set; } + //[Required(ErrorMessage = "巡查损坏等级不能为空")] + public int DamageGrade { get; set; } + //[Required(ErrorMessage = "巡查综合等级不能为空")] + public int ComprehensiveGrade { get; set; } + #region 调查情况investigation + /// + /// 房屋场地 + /// + public string HouseSite { get; set; } + + /// + /// 相邻施工 + /// + public string AdjacentConstruction { get; set; } + + /// + /// 化学侵蚀 + /// + public string ChemicalErosion { get; set; } + + /// + /// 结构拆改 + /// + public int? StructuralDismantling { get; set; } + + /// + /// 加层改造 + /// + public int? AddingLayer { get; set; } + + /// + /// 修缮加固 + /// + public string RepairAndReinforce { get; set; } + + /// + /// 历史灾害 + /// + public string HistoricalCalamity { get; set; } + + /// + /// 使用功能变更 + /// + public string FunctionalChange { get; set; } + + /// + /// 其他调查内容 + /// + public string OtherContents { get; set; } + #endregion + /// + /// 主要安全隐患综述 + /// + public string MainSafety { get; set; } + + /// + /// 处理意见 + /// + public int? HandlingOpinion { get; set; } + + /// + /// 处理意见备注 + /// + public string HandlingOpinionRemark { get; set; } + + /// + /// 整改情况 + /// + public int? RectifyAndReform { get; set; } + + /// + /// 整改情况备注 + /// + public string RectifyAndReformRemark { get; set; } + + /// + /// 巡查结果:1正常,-1异常 + /// + //[Required(ErrorMessage = "巡查结果不能为空")] + public int PatrolResult { get; set; } + + public string PatrolResultRemark { get; set; } + + /// + /// 上报街道 + /// + public int? ReportRoad { get; set; } + + /// + /// 上报街道时间 + /// + public DateTime? ReportRoadTime { get; set; } + + /// + /// 上报区住建 + /// + public int? ReportArea { get; set; } + + /// + /// 上报区住建时间 + /// + public DateTime? ReportAreaTime { get; set; } + + /// + /// 上报备注 + /// + public string ReportRemark { get; set; } + } + + public class AddHouseTaskInput : HouseTaskInput + { + + } + + public class EditHouseTaskInput : HouseTaskInput + { + [Required(ErrorMessage = "任务Id不能为空")] + public string Id { get; set; } + } + + public class SubmitHouseTaskInput + { + [Required(ErrorMessage = "任务Id不能为空")] + public string TaskId { get; set; } + } + + public class QueryHouseTaskInput : PageInputBase + { + + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseTask/Dto/HouseTaskOutput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseTask/Dto/HouseTaskOutput.cs new file mode 100644 index 0000000..b5f1df0 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseTask/Dto/HouseTaskOutput.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseTaskOutput + { + public DateTime? PatrolDate { get; set; } + public string PatrolUser { get; set; } + + /// + /// 沉降倾斜 + /// + public string SettlementTilt { get; set; } + + /// + /// 沉降倾斜附件 + /// + public string SettlementTiltFiles { get; set; } + + /// + /// 其他情况 + /// + public string OtherInfo { get; set; } + + /// + /// 其他情况附件 + /// + public string OtherInfoFiles { get; set; } + + /// + /// 初始等级 + /// + public int? InitGrade { get; set; } + + /// + /// 损坏等级 + /// + public int? DamageGrade { get; set; } + + /// + /// 综合等级 + /// + public int? ComprehensiveGrade { get; set; } + + #region 调查情况investigation + /// + /// 房屋场地 + /// + public string HouseSite { get; set; } + + /// + /// 相邻施工 + /// + public string AdjacentConstruction { get; set; } + + /// + /// 化学侵蚀 + /// + public string ChemicalErosion { get; set; } + + /// + /// 结构拆改 + /// + public int? StructuralDismantling { get; set; } + + /// + /// 加层改造 + /// + public int? AddingLayer { get; set; } + + /// + /// 修缮加固 + /// + public string RepairAndReinforce { get; set; } + + /// + /// 历史灾害 + /// + public string HistoricalCalamity { get; set; } + + /// + /// 使用功能变更 + /// + public string FunctionalChange { get; set; } + + /// + /// 其他调查内容 + /// + public string OtherContents { get; set; } + #endregion + + /// + /// 主要安全隐患综述 + /// + public string MainSafety { get; set; } + + /// + /// 处理意见 + /// + public int? HandlingOpinion { get; set; } + + /// + /// 处理意见备注 + /// + public string HandlingOpinionRemark { get; set; } + + /// + /// 整改情况 + /// + public int? RectifyAndReform { get; set; } + + /// + /// 整改情况备注 + /// + public string RectifyAndReformRemark { get; set; } + + /// + /// 巡查结果:1正常,-1异常 + /// + public int? PatrolResult { get; set; } + + /// + /// 巡查异常描述 + /// + public string PatrolResultRemark { get; set; } + + /// + /// 上报街道 + /// + public int? ReportRoad { get; set; } + + /// + /// 上报街道时间 + /// + public DateTime? ReportRoadTime { get; set; } + + /// + /// 上报区住建 + /// + public int? ReportArea { get; set; } + + /// + /// 上报区住建时间 + /// + public DateTime? ReportAreaTime { get; set; } + + /// + /// 上报备注 + /// + public string ReportRemark { get; set; } + + public int Status { get; set; } + + /// + /// 提交时间 + /// + public DateTime? SubmitTime { get; set; } + + /// + /// 最后提交时间 + /// + public DateTime? LastSubmitTime { get; set; } + + /// + /// 是否有效 + /// + public bool IsEnabled { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseTask/HouseTaskService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseTask/HouseTaskService.cs new file mode 100644 index 0000000..6337437 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseTask/HouseTaskService.cs @@ -0,0 +1,102 @@ +using Dapper; +using Ewide.Core; +using Ewide.Core.Extension; +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + /// + /// 房屋任务(巡查任务/建档任务) + /// + [ApiDescriptionSettings(Name = "HouseTask", Order = 190)] + public class HouseTaskService : IHouseTaskService, IDynamicApiController, ITransient + { + private readonly IRepository _houseTaskRep; + private readonly IRepository _houseInfoRep; + private readonly IDapperRepository _dapperRepository; + private readonly IUserManager _userManager; + + private readonly IHouseLogService _houseLogService; + + public HouseTaskService(IRepository HouseTaskRep, IRepository HouseInfoRep, IDapperRepository dapperRepository, IUserManager userManager, IHouseLogService houseLogService) + { + _houseTaskRep = HouseTaskRep; + _houseInfoRep = HouseInfoRep; + _dapperRepository = dapperRepository; + _userManager = userManager; + + _houseLogService = houseLogService; + } + + [HttpPost("/houseTask/page")] + public async Task QueryPage([FromBody] QueryHouseTaskInput input) + { + var sql = @"SELECT T.Id,AA.Name AreaName,RA.Name RoadName,CA.Name CommName,Proj.AreaCode,Proj.Note,Proj.Name,CONCAT(Proj.`Name`, IFNULL(CONCAT('(',Proj.Note,')'), '')) FullProjName,HC.HouseCode,HC.Address,T.EndTime,HC.Type,HC.Industry,HC.No,T.Status,IFNULL(HI.State,0) State FROM `bs_house_task` T +LEFT JOIN bs_house_code HC ON T.HouseCodeId = HC.Id +LEFT JOIN bs_house_info HI ON HI.HouseCodeId = T.HouseCodeId +LEFT JOIN bs_house_projectinfo Proj ON Proj.Id=HC.ProjectId +LEFT JOIN sys_area_code CA ON CA.Code = Proj.AreaCode +LEFT JOIN sys_area_code RA ON RA.AdCode = SUBSTR(CA.AdCode,1,9) +LEFT JOIN sys_area_code AA ON AA.AdCode = SUBSTR(CA.AdCode,1,6) +WHERE T.IsEnabled = 1 {0}"; + var user = await _userManager.CheckUserAsync(); + if (user == null) throw Oops.Oh("登录信息丢失"); + + var userRoles = await _userManager.GetUserRoleList(); + var userOrg = await _userManager.GetUserOrgInfo(); + var param = new DynamicParameters(); + if (userRoles.Where(r => r.Code == Enum.GetName(HouseManagerRole.HouseSecurityManager).ToUnderScoreCase()).Any()) + { + sql = String.Format(sql, " AND T.UserID=@UserID "); + param.Add("UserID", user.Id); + } + + if (userRoles.Where(r => r.Code == Enum.GetName(HouseManagerRole.ZoneManager).ToUnderScoreCase()).Any()) + { + sql = String.Format(sql, " AND (T.Status=3 OR T.Status=6) AND HC.ZoneId = @ZoneId "); + param.Add("ZoneId", userOrg.Id); + } + + return await _dapperRepository.QueryPageDataDynamic(sql, input, param, filterFields: new string[] { "Type", "Address", "HouseCode", "Status","State" }); + } + + [HttpPost("/houseTask/submit")] + [UnitOfWork] + [AllowAnonymous] + public async Task Submit([FromBody] SubmitHouseTaskInput input) + { + //获取任务实体 + var houseTask = await _houseTaskRep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == input.TaskId); + if (houseTask == null) throw Oops.Oh("任务不存在"); + houseTask.Status = 2; + houseTask.LastSubmitTime = DateTime.Now; + if (!houseTask.SubmitTime.HasValue) houseTask.SubmitTime = houseTask.LastSubmitTime; + await houseTask.UpdateExcludeAsync(new string[] { nameof(BsHouseTask.TaskType) }, ignoreNullValues: true); + + if(houseTask.TaskType == 0) + { + var houseInfo = await _houseInfoRep.DetachedEntities.FirstOrDefaultAsync(h => h.HouseCodeId == houseTask.HouseCodeId); + if (houseInfo == null) throw Oops.Oh("提交审核失败:请先保存后再提交"); + + houseInfo.State = 5; + await houseInfo.UpdateExcludeAsync(new[] { nameof(BsHouseInfo.HouseGrade) }, true); + } + + // 提交时流转日志 + await _houseLogService.Done(houseTask.HouseCodeId); + } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseTask/IHouseTaskService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseTask/IHouseTaskService.cs new file mode 100644 index 0000000..75b2d3d --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseTask/IHouseTaskService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public interface IHouseTaskService + { + Task QueryPage(QueryHouseTaskInput input); + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseTaskCheckRecord/Dto/HouseTaskCheckRecordInput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseTaskCheckRecord/Dto/HouseTaskCheckRecordInput.cs new file mode 100644 index 0000000..497a3a7 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseTaskCheckRecord/Dto/HouseTaskCheckRecordInput.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseTaskCheckRecordInput + { + /// + /// 任务Id + /// + [Required(ErrorMessage = "任务参数有误")] + public string TaskId { get; set; } + + /// + /// 通过3;退回-1 + /// + [Required(ErrorMessage = "审核结果不能为空")] + public int PassOrBack { get; set; } + + public DataStatus PassOrBackDataStatus + { + get + { + return PassOrBack switch + { + -1 => DataStatus.Back, + 1 => DataStatus.TempSaved, + 2 => DataStatus.Saved, + 3 => DataStatus.Submited, + 6 => DataStatus.Passed, + _ => DataStatus.Init, + }; + } + } + + /// + /// 审核内容 + /// + [Required(ErrorMessage = "审核内容不能为空")] + public string Content { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseTaskCheckRecord/Dto/HouseTaskCheckRecordOutput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseTaskCheckRecord/Dto/HouseTaskCheckRecordOutput.cs new file mode 100644 index 0000000..bdfa2ab --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseTaskCheckRecord/Dto/HouseTaskCheckRecordOutput.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service.HouseSafety.HouseTaskCheckRecord.Dto +{ + public class HouseTaskCheckRecordOutput + { + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseZone/Dto/HouseZoneInput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseZone/Dto/HouseZoneInput.cs new file mode 100644 index 0000000..1d69b94 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseZone/Dto/HouseZoneInput.cs @@ -0,0 +1,41 @@ +using Ewide.Core.Service; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class HouseZoneInput + { + [Required(ErrorMessage = "区域编码不可为空")] + [MinLength(9, ErrorMessage = "区域编码长度必须为9位及以上")] + public string AreaCode { get; set; } + } + + public class AddHouseZoneInput : OrgInput + { + /// + /// 所属街道 + /// + [Required(ErrorMessage = "所属街道不能为空")] + public override string Pid { get; set; } + /// + /// 名称 + /// + [Required(ErrorMessage = "片区名称不能为空")] + public override string Name { get; set; } + + } + + public class UpdateHouseZoneInput : AddHouseZoneInput + { + /// + /// 机构Id + /// + [Required(ErrorMessage = "片区Id不能为空")] + public string Id { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseZone/Dto/HouseZoneOutput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseZone/Dto/HouseZoneOutput.cs new file mode 100644 index 0000000..d95ac21 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseZone/Dto/HouseZoneOutput.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + class HouseZoneOutput + { + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseZone/HouseZoneService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseZone/HouseZoneService.cs new file mode 100644 index 0000000..791374b --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseZone/HouseZoneService.cs @@ -0,0 +1,175 @@ +using Ewide.Core; +using Ewide.Core.Extension; +using Ewide.Core.Service; +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + /// + /// 片区相关 + /// + [ApiDescriptionSettings(Name = "HouseZone", Order = 180)] + public class HouseZoneService : IHouseZoneService, IDynamicApiController, ITransient + { + private readonly IUserManager _userManager; + private readonly IRepository _sysOrgRep; + private readonly IRepository _sysEmpRep; + private readonly ISysOrgService _sysOrgService; + + public HouseZoneService( + IUserManager userManager, + IRepository sysOrgRep, + IRepository sysEmpRep, + ISysOrgService sysOrgService + ) + { + _userManager = userManager; + _sysOrgRep = sysOrgRep; + _sysEmpRep = sysEmpRep; + _sysOrgService = sysOrgService; + } + + /// + /// 获取片区列表 + /// + /// + /// + [HttpGet("/houseZone/list")] + public async Task GetHouseZoneList([FromQuery] HouseZoneInput input) + { + var areaCode = input.AreaCode.Substring(0, 9); + var road = await _sysOrgRep.DetachedEntities.FirstOrDefaultAsync(p => p.AreaCode == areaCode); + if (road == null) throw Oops.Oh("未在组织机构中配置街道"); + return await _sysOrgRep.DetachedEntities + .Where(p => p.Pid == road.Id) + .Where(p => p.Type == (int)OrgType.片区) + .OrderBy(p => p.Sort) + .Select(p => new + { + p.Id, + p.Name + }) + .ToListAsync(); + } + + [HttpGet("/houseZone/getById")] + public async Task GetById([Required] string zoneId) + { + return await _sysOrgRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == zoneId); + } + + /// + /// 分页查询片区 + /// + /// + /// + [HttpPost("/houseZone/page")] + public async Task QueryZonePageList([FromBody] PageOrgInput input) + { + var dataScopeList = _sysOrgService.GetDataScopeList(await _userManager.GetUserAllDataScopeList()); + + var name = !string.IsNullOrEmpty(input.Name?.Trim()); + var id = !string.IsNullOrEmpty(input.Id?.Trim()); + var pId = !string.IsNullOrEmpty(input.Pid?.Trim()); + var orgs = await _sysOrgRep.DetachedEntities + .Where((name, u => EF.Functions.Like(u.Name, $"%{input.Name.Trim()}%")), // 根据机构名称模糊查询 + (id, u => u.Id == input.Id.Trim()), // 根据机构id查询 + (pId, u => EF.Functions.Like(u.Pids, $"%[{input.Pid.Trim()}]%") + || u.Id == input.Pid.Trim())) // 根据父机构id查询 + .Where(dataScopeList.Count > 0, u => dataScopeList.Contains(u.Id)) // 非管理员范围限制 + .Where(u => u.Status != CommonStatus.DELETED) + .Where(u => u.Type == (int)OrgType.片区) + .OrderBy(u => u.Sort) + .ToPageData(input); + return PageDataResult.PageResult(orgs); + } + + /// + /// 根据用户Id获取所在片区的Id + /// + /// + /// + [HttpGet("/houseZone/getByUser")] + public async Task GetZoneByUser([FromQuery][Required(ErrorMessage = "用户Id不能为空")] string userId) + { + var data = await _sysEmpRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == userId); + if(data == null) throw Oops.Oh("用户不在组织机构中"); + var org = await _sysOrgRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == data.OrgId && p.Type == (int)OrgType.片区); + if(org == null) throw Oops.Oh("用户不在片区中"); + return org.Id; + } + + [HttpGet("/houseZone/autoIncrement")] + public async Task AutoIncrement([FromQuery] string roadId) + { + var road = await _sysOrgRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == roadId && p.Type == 3); + if (road == null) throw Oops.Oh("组织机构错误"); + return await AutoIncrement(road); + } + + [NonAction] + public async Task AutoIncrement(SysOrg road) + { + var maxZone = (await _sysOrgRep.DetachedEntities.Where(p => p.Pid == road.Id && p.Type == (int)OrgType.片区).MaxAsync(p => p.Code)) ?? "000"; + return Convert.ToInt32(maxZone[^3..]) + 1; + } + + [HttpPost("/houseZone/add")] + public async Task AddZone(AddHouseZoneInput input) + { + /* + * 区县市限定所属区域/上级机构是否为当前区 + * 街道自动获取所属区域/上级机构 + * 自动生成唯一编码, 街道Code+三位编号 + * + * 机构类型默认为片区 + */ + + var org = await _userManager.GetUserOrgInfo(); + + var areaManager = Enum.GetName(HouseManagerRole.AreaManager).ToUnderScoreCase(); + var roadManager = Enum.GetName(HouseManagerRole.RoadManager).ToUnderScoreCase(); + var roles = await _userManager.GetUserRoleList(); + if (roles.Any(p => p.Code == areaManager)) + { + var road = await _sysOrgRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == input.Pid); + if (!road.Pids.Contains(org.Id)) throw Oops.Oh("当前用户组织机构错误"); + + input.AreaCode = road.AreaCode; + input.Code = road.Code + (await AutoIncrement(road)).ToString().PadLeft(3, '0'); + } + else if (roles.Any(p => p.Code == roadManager)) + { + input.AreaCode = org.AreaCode; + + input.Code = org.Code + (await AutoIncrement(org)).ToString().PadLeft(3, '0'); + } + + input.Type = (int)OrgType.片区; + + AddOrgInput addOrgInput = input.Adapt(); + await _sysOrgService.AddOrg(addOrgInput); + } + + [HttpPost("/houseZone/edit")] + public async Task EditZone(UpdateHouseZoneInput input) + { + var zone = await _sysOrgRep.DetachedEntities.FirstOrDefaultAsync(z => z.Id == input.Id); + if(zone == null) throw Oops.Oh("修改失败:数据有误,刷新列表后再尝试修改"); + zone.Remark = input.Remark; + await zone.UpdateIncludeAsync(new[] { nameof(SysOrg.Remark) }, true); + } + } +} diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseZone/IHouseZoneService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseZone/IHouseZoneService.cs new file mode 100644 index 0000000..9c0b138 --- /dev/null +++ b/Api/Ewide.Application/Service/HouseSafety/HouseZone/IHouseZoneService.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public interface IHouseZoneService + { + Task GetHouseZoneList([FromQuery] HouseZoneInput input); + Task GetZoneByUser([FromQuery] string userId); + } +} diff --git a/Api/Ewide.Application/Service/Inspection/InspectionOrg/Dto/InspectionOrgInput.cs b/Api/Ewide.Application/Service/Inspection/InspectionOrg/Dto/InspectionOrgInput.cs new file mode 100644 index 0000000..483f135 --- /dev/null +++ b/Api/Ewide.Application/Service/Inspection/InspectionOrg/Dto/InspectionOrgInput.cs @@ -0,0 +1,81 @@ +using Ewide.Core; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class InspectionOrgKeyInput + { + public virtual string Id { get; set; } + } + + public class InspectionOrgKeyRequiredInput : InspectionOrgKeyInput + { + [Required] + public override string Id { get; set; } + } + + public class InspectionOrgPageInput : PageInputBase + { + + } + + public class InspectionOrgRegisterInput + { + [MaxLength(100)] + [Required] + public string Name { get; set; } + + [MaxLength(50)] + [Required] + public string CreditCode { get; set; } + + [Required] + public string Address { get; set; } + + [MaxLength(50)] + [Required] + public string Contacts { get; set; } + + [MaxLength(50)] + [Required] + public string ContactsPhone { get; set; } + + [MaxLength(50)] + [Required] + public string LegalPerson { get; set; } + + [MaxLength(50)] + public string OrgTelephone { get; set; } + + [Required] + public string ApplicationFormFiles { get; set; } + + [Required] + public string OrgBusinessLicenseFiles { get; set; } + + [Required] + public string InspectionQualificationCertificateFiles { get; set; } + + [Required] + public string OfficeInformationFiles { get; set; } + + [Required] + public string EmployeeCertificateFiles { get; set; } + + [Required] + public string CalibrationCertificateFiles { get; set; } + + public string OtherFiles { get; set; } + } + + public class InspectionOrgReviewNotApprovedInput : InspectionOrgKeyRequiredInput + { + [Required] + public string Remark { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/Inspection/InspectionOrg/Dto/InspectionOrgOutput.cs b/Api/Ewide.Application/Service/Inspection/InspectionOrg/Dto/InspectionOrgOutput.cs new file mode 100644 index 0000000..bb6b154 --- /dev/null +++ b/Api/Ewide.Application/Service/Inspection/InspectionOrg/Dto/InspectionOrgOutput.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class InspectionOrgDetailOutput + { + public string Id { get; set; } + public string Code { get; set; } + public InspectionOrgStatus Status { get; set; } + public InspectionOrgUpdateStatus UpdateStatus { get; set; } + public string Remark { get; set; } + public DateTime? CreatedTime { get; set; } + public DateTime? UpdatedTime { get; set; } + public InspectionOrgRegisterInput Record { get; set; } + public InspectionOrgRegisterInput UpdateRecord { get; set; } + } + + public class InspectionRegisterStatusOutput + { + public DateTime BeginDate { get; set; } + public DateTime EndDate { get; set; } + public InspectionOrgDetailOutput InspectionOrg { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/Inspection/InspectionOrg/IInspectionOrgService.cs b/Api/Ewide.Application/Service/Inspection/InspectionOrg/IInspectionOrgService.cs new file mode 100644 index 0000000..a0ad78c --- /dev/null +++ b/Api/Ewide.Application/Service/Inspection/InspectionOrg/IInspectionOrgService.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public interface IInspectionOrgService + { + public Task NewCode(); + public Task RegisterStatus(); + public Task Register(InspectionOrgRegisterInput input); + public Task Update(InspectionOrgRegisterInput input); + public Task Detail(InspectionOrgKeyInput input); + public Task ReviewPage(InspectionOrgPageInput input); + public Task ReviewUpdatePage(InspectionOrgPageInput input); + public Task ReviewApproved(InspectionOrgKeyRequiredInput input); + public Task ReviewNotApproved(InspectionOrgReviewNotApprovedInput input); + public Task History(InspectionOrgKeyRequiredInput input); + } +} diff --git a/Api/Ewide.Application/Service/Inspection/InspectionOrg/InspectionOrgService.cs b/Api/Ewide.Application/Service/Inspection/InspectionOrg/InspectionOrgService.cs new file mode 100644 index 0000000..7561ec7 --- /dev/null +++ b/Api/Ewide.Application/Service/Inspection/InspectionOrg/InspectionOrgService.cs @@ -0,0 +1,410 @@ +using Dapper; +using Ewide.Core; +using Ewide.Core.Extension; +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Furion.JsonSerialization; +using Mapster; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + /// + /// 鉴定机构相关 + /// + [ApiDescriptionSettings(Name = "InspectionOrg")] + public class InspectionOrgService : IInspectionOrgService, IDynamicApiController, ITransient + { + private readonly IJsonSerializerProvider _jsonSerializer; + private readonly IDapperRepository _dapperRep; + + private readonly IUserManager _userManager; + + private readonly IRepository _orgRep; + private readonly IRepository _orgUpdateRep; + private readonly IRepository _relationRep; + private readonly IRepository _settingRep; + + public InspectionOrgService( + IJsonSerializerProvider jsonSerializer, + IDapperRepository dapperRep, + IUserManager userManager, + IRepository orgRep, + IRepository orgUpdateRep, + IRepository relationRep, + IRepository settingRep + ) + { + _jsonSerializer = jsonSerializer; + _dapperRep = dapperRep; + + _userManager = userManager; + + _orgRep = orgRep; + _orgUpdateRep = orgUpdateRep; + _relationRep = relationRep; + _settingRep = settingRep; + } + + /// + /// 获取可登记的时间范围 + /// + /// + private async Task DateRange() + { + // 验证当前时间是否已在可申报时间范围内 + var setting = await _settingRep.DetachedEntities.FirstOrDefaultAsync(p => p.Type == InspectionOrgSettingType.DateRange); + var dateRange = _jsonSerializer.Deserialize(setting.Setting); + return dateRange; + } + + /// + /// 获取当前用户所在机构 + /// + /// + private async Task RelationOrgId() + { + var relation = await _relationRep.DetachedEntities.FirstOrDefaultAsync(p => p.SysUserId.Equals(_userManager.UserId)); + if (relation == null) + { + return null; + } + + return relation.InspectionOrgId; + } + + /// + /// 获取最新备案编号 + /// + /// + [HttpGet("/inspectionOrg/newCode")] + public async Task NewCode() + { + var nowYear = DateTime.Now.Year; + var code = await _orgRep.DetachedEntities.MaxAsync(p => p.Code); + if (String.IsNullOrEmpty(code)) + { + code = nowYear + "001"; + } + else + { + var year = int.Parse(code.Substring(0, 4)); + var no = int.Parse(code.Substring(4, 3)); + if (year < nowYear) + { + code = nowYear + "001"; + } + else + { + code = year + (no + 1).ToString().PadLeft(3, '0'); + } + } + + return code; + } + + /// + /// 获取当前用户所在鉴定机构信息及登记时间范围 + /// + /// + [HttpGet("/inspectionOrg/registerStatus")] + public async Task RegisterStatus() + { + var orgId = await RelationOrgId(); + var dateRange = await DateRange(); + var output = dateRange.Adapt(); + if (String.IsNullOrEmpty(orgId)) + { + return output; + } + + var org = await _orgRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id.Equals(orgId)); + var orgUpdate = await _orgUpdateRep.DetachedEntities.Where(p => p.OrgId.Equals(orgId)).OrderByDescending(p => p.CreatedTime).FirstOrDefaultAsync(); + output.InspectionOrg = org.Adapt(); + output.InspectionOrg.UpdateStatus = orgUpdate.Status; + output.InspectionOrg.Remark = orgUpdate.Remark; + return output; + } + + /// + /// 鉴定机构登记 + /// + /// + /// + [HttpPost("/inspectionOrg/register")] + [UnitOfWork] + public async Task Register([FromBody] InspectionOrgRegisterInput input) + { + // 验证当前时间是否已在可申报时间范围内 + var dateRange = await DateRange(); + var now = DateTime.Now; + if (!(now >= dateRange.BeginDate && now < dateRange.EndDate.AddDays(1))) + throw Oops.Oh("当前时间段无法登记鉴定机构"); + + // 登记时主表数据应为空, 只向记录数据中插入当前填表的内容 + var updateData = input.Adapt(); + var orgId = await RelationOrgId(); + if (String.IsNullOrEmpty(orgId)) + { + orgId = Guid.NewGuid().ToString().ToLower(); + var code = await NewCode(); + // 登记主表 + var data = new BsInspectionOrg + { + Id = orgId, + Code = code, + }; + await data.InsertAsync(); + + // 用户关系表 + await new BsInspectionMemberRelation + { + SysUserId = _userManager.UserId, + InspectionOrgId = orgId + }.InsertAsync(); + } + // 登记记录表 + updateData.OrgId = orgId; + updateData.Type = InspectionOrgUpdateType.Register; + updateData.Status = InspectionOrgUpdateStatus.Reviewing; + await updateData.InsertAsync(); + } + + /// + /// 鉴定机构申请修改 + /// + /// + /// + [HttpPost("/inspectionOrg/update")] + public async Task Update([FromBody] InspectionOrgRegisterInput input) + { + var orgId = await RelationOrgId(); + var org = await _orgRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id.Equals(orgId)); + var updateData = input.Adapt(); + // 与之前的值进行对比, 没有变更则不提交 + var updated = false; + var TSource = org.GetType(); + var TSourceProperties = TSource.GetProperties(); + var TTarget = updateData.GetType(); + var TTargetProperties = TTarget.GetProperties(); + var compareFields = new[] { + nameof(BsInspectionOrg.Name), + nameof(BsInspectionOrg.CreditCode), + nameof(BsInspectionOrg.Address), + nameof(BsInspectionOrg.Contacts), + nameof(BsInspectionOrg.ContactsPhone), + nameof(BsInspectionOrg.LegalPerson), + nameof(BsInspectionOrg.OrgTelephone), + nameof(BsInspectionOrg.ApplicationFormFiles), + nameof(BsInspectionOrg.OrgBusinessLicenseFiles), + nameof(BsInspectionOrg.InspectionQualificationCertificateFiles), + nameof(BsInspectionOrg.OfficeInformationFiles), + nameof(BsInspectionOrg.EmployeeCertificateFiles), + nameof(BsInspectionOrg.CalibrationCertificateFiles), + nameof(BsInspectionOrg.OtherFiles) + }; + + foreach (var fields in compareFields) + { + var PSource = TSourceProperties.FirstOrDefault(p => p.Name.Equals(fields)); + var VSource = PSource.GetValue(org); + var PTarget = TTargetProperties.FirstOrDefault(p => p.Name.Equals(fields)); + var VTarget = PTarget.GetValue(updateData); + if ( + (VSource == null && VTarget != null) + || + (VSource != null && !VSource.Equals(VTarget)) + ) + { + updated = true; + break; + } + } + + if (!updated) + { + throw Oops.Oh("当前信息未修改"); + } + + // 登记记录表 + updateData.OrgId = orgId; + updateData.Type = InspectionOrgUpdateType.Update; + updateData.Status = InspectionOrgUpdateStatus.Reviewing; + await updateData.InsertAsync(); + } + + /// + /// 鉴定机构详情 + /// + /// + /// + [HttpGet("/inspectionOrg/detail")] + public async Task Detail([FromQuery] InspectionOrgKeyInput input) + { + var orgId = String.Empty; + // 传入Id表示查看他人,否则查看自己 + if (!String.IsNullOrEmpty(input.Id)) + { + orgId = input.Id; + } + else + { + orgId = await RelationOrgId(); + } + + if (String.IsNullOrEmpty(orgId)) + return null; + + var output = new InspectionOrgDetailOutput(); + + var org = await _orgRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id.Equals(orgId)); + var orgUpdate = await _orgUpdateRep.DetachedEntities.Where(p => p.OrgId.Equals(orgId)).OrderByDescending(p => p.CreatedTime).FirstOrDefaultAsync(); + + if (org.Status == InspectionOrgStatus.Normal) + { + output.Record = org.Adapt(); + } + if (orgUpdate != null && + (orgUpdate.Status == InspectionOrgUpdateStatus.Reviewing || orgUpdate.Status == InspectionOrgUpdateStatus.Back)) + { + output.UpdateRecord = orgUpdate.Adapt(); + } + + // 获取最新更新时间 + var lastOrgUpdatedTime = await _orgUpdateRep.DetachedEntities + .Where(p => p.OrgId.Equals(orgId)) + .Where(p => p.Status == InspectionOrgUpdateStatus.Approved) + .MaxAsync(p => p.UpdatedTime); + + output.Id = orgId; + output.Code = org.Code; + output.Status = org.Status; + output.UpdateStatus = orgUpdate.Status; + output.Remark = orgUpdate.Remark; + output.CreatedTime = org.CreatedTime; + output.UpdatedTime = lastOrgUpdatedTime; + + return output; + } + + private async Task Page(InspectionOrgPageInput input, int Type) + { + var sql = @"SELECT + IO.Id, + IO.Code, + IOU.Name, + IOU.CreditCode, + IOU.Address, + IOU.Contacts, + IOU.ContactsPhone, + IOU.LegalPerson, + IOU.OrgTelephone, + IO.Status, + IOU.Type UpdateType, + IOU.Status UpdateStatus, + (SELECT COUNT(0) FROM bs_inspection_org_update WHERE OrgId = IO.Id AND Status <> 1) UpdateCount, + IOU.UpdatedTime +FROM bs_inspection_org IO +LEFT JOIN +(SELECT * FROM bs_inspection_org_update WHERE CreatedTime IN (SELECT MAX(CreatedTime) FROM bs_inspection_org_update WHERE Status <> -2 AND Type = @UpdateType GROUP BY OrgId)) +IOU ON IOU.OrgId = IO.Id"; + + return await _dapperRep.QueryPageDataDynamic( + sql, + input, + param: new { UpdateType = Type }, + filterFields: new[] { "Code", "UpdateStatus" } + ); + } + + [HttpPost("/inspectionOrg/reviewPage")] + public async Task ReviewPage([FromBody] InspectionOrgPageInput input) + { + return await Page(input, 1); + } + + [HttpPost("/inspectionOrg/reviewUpdatePage")] + public async Task ReviewUpdatePage([FromBody] InspectionOrgPageInput input) + { + return await Page(input, 2); + } + + /// + /// 登记审核通过 + /// + /// + /// + [HttpPost("/inspectionOrg/reviewApproved")] + [UnitOfWork] + public async Task ReviewApproved([FromBody] InspectionOrgKeyRequiredInput input) + { + var orgUpdate = await _orgUpdateRep.DetachedEntities.Where(p => p.OrgId.Equals(input.Id)).OrderByDescending(p => p.CreatedTime).FirstOrDefaultAsync(); + + orgUpdate.Status = InspectionOrgUpdateStatus.Approved; + await orgUpdate.UpdateIncludeAsync(new [] { + nameof(BsInspectionOrgUpdate.Status) + }); + + var org = orgUpdate.Adapt(); + org.Id = input.Id; + org.Status = InspectionOrgStatus.Normal; + + await org.UpdateExcludeAsync(new[] { + nameof(BsInspectionOrg.Code) + }); + } + + /// + /// 登记审核退回 + /// + /// + /// + [HttpPost("/inspectionOrg/reviewNotApproved")] + public async Task ReviewNotApproved([FromBody] InspectionOrgReviewNotApprovedInput input) + { + var orgUpdate = await _orgUpdateRep.DetachedEntities.Where(p => p.OrgId.Equals(input.Id)).OrderByDescending(p => p.CreatedTime).FirstOrDefaultAsync(); + + orgUpdate.Status = InspectionOrgUpdateStatus.Back; + orgUpdate.Remark = input.Remark; + await orgUpdate.UpdateIncludeAsync(new[] { + nameof(BsInspectionOrgUpdate.Status), + nameof(BsInspectionOrgUpdate.Remark) + }); + } + + /// + /// 获取审核记录 + /// + /// + /// + [HttpGet("/inspectionOrg/history")] + public async Task History([FromQuery] InspectionOrgKeyRequiredInput input) + { + var sql = @"SELECT + IOU.Id, + IOU.OrgId, + IO.Code, + IOU.Remark, + IOU.Type, + IOU.Status, + IOU.CreatedTime, + IOU.UpdatedTime +FROM bs_inspection_org_update IOU +LEFT JOIN bs_inspection_org IO ON IO.Id = IOU.OrgId +WHERE IO.Id = @Id + AND IOU.Status <> 1 -- 审核中 +ORDER BY CreatedTime DESC"; + return await _dapperRep.QueryAsync(sql, new { Id = input.Id }); + } + } +} diff --git a/Api/Ewide.Application/Service/Inspection/InspectionOrgDir/Dto/InspectionOrgDirInput.cs b/Api/Ewide.Application/Service/Inspection/InspectionOrgDir/Dto/InspectionOrgDirInput.cs new file mode 100644 index 0000000..1975c48 --- /dev/null +++ b/Api/Ewide.Application/Service/Inspection/InspectionOrgDir/Dto/InspectionOrgDirInput.cs @@ -0,0 +1,42 @@ +using Ewide.Core; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class InspectionOrgDirKeyInput + { + public virtual string Id { get; set; } + } + + public class InspectionOrgDirKeyRequiredInput : InspectionOrgDirKeyInput + { + [Required] + public override string Id { get; set; } + } + + public class InspectionOrgDirPageInput : PageInputBase + { + + } + + public class InspectionOrgDirSaveInput : InspectionOrgDirKeyInput + { + [Required] + public string No { get; set; } + [Required] + public InspectionOrgDirSaveDetailInput[] Detail { get; set; } + } + + public class InspectionOrgDirSaveDetailInput + { + [Required] + public string OrgId { get; set; } + [Required] + public int Score { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/Inspection/InspectionOrgDir/Dto/InspectionOrgDirOutput.cs b/Api/Ewide.Application/Service/Inspection/InspectionOrgDir/Dto/InspectionOrgDirOutput.cs new file mode 100644 index 0000000..52da4bc --- /dev/null +++ b/Api/Ewide.Application/Service/Inspection/InspectionOrgDir/Dto/InspectionOrgDirOutput.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application +{ + public class InspectionOrgDirPreviewOutput + { + public BsInspectionOrgDirStage Stage { get; set; } + public int Count { get; set; } + public dynamic List { get; set; } + } +} diff --git a/Api/Ewide.Application/Service/Inspection/InspectionOrgDir/IInspectionOrgDirService.cs b/Api/Ewide.Application/Service/Inspection/InspectionOrgDir/IInspectionOrgDirService.cs new file mode 100644 index 0000000..0419301 --- /dev/null +++ b/Api/Ewide.Application/Service/Inspection/InspectionOrgDir/IInspectionOrgDirService.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public interface IInspectionOrgDirService + { + public Task Page(InspectionOrgDirPageInput input); + public Task ListScore(); + public Task Detail(); + public Task SaveScore(InspectionOrgDirSaveInput input); + } +} diff --git a/Api/Ewide.Application/Service/Inspection/InspectionOrgDir/InspectionOrgDirService.cs b/Api/Ewide.Application/Service/Inspection/InspectionOrgDir/InspectionOrgDirService.cs new file mode 100644 index 0000000..d46ccbb --- /dev/null +++ b/Api/Ewide.Application/Service/Inspection/InspectionOrgDir/InspectionOrgDirService.cs @@ -0,0 +1,295 @@ +using Dapper; +using Ewide.Core.Extension; +using Furion.DatabaseAccessor; +using Furion.DatabaseAccessor.Extensions; +using Furion.DependencyInjection; +using Furion.DynamicApiController; +using Furion.FriendlyException; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public class InspectionOrgDirService : IInspectionOrgDirService, IDynamicApiController, ITransient + { + private readonly IDapperRepository _dapperRep; + + private readonly IRepository _dirStageRep; + private readonly IRepository _dirDetailRep; + private readonly IRepository _orgRep; + + public InspectionOrgDirService( + IDapperRepository dapperRep, + + IRepository dirStageRep, + IRepository dirDetailRep, + IRepository orgRep + ) + { + _dapperRep = dapperRep; + + _dirStageRep = dirStageRep; + _dirDetailRep = dirDetailRep; + _orgRep = orgRep; + } + + [HttpPost("/inspectionOrgDir/page")] + public async Task Page([FromBody] InspectionOrgDirPageInput input) + { + var sql = "SELECT * FROM bs_inspection_org_dir_stage WHERE Status = 1 ORDER BY PublishTime DESC"; + return await _dapperRep.QueryPageDataDynamic(sql, input, filterFields: new[] { nameof(BsInspectionOrgDirStage.No) }); + } + + /// + /// 获取当前名录打分列表 + /// + /// + [HttpPost("/inspectionOrgDir/listScore")] + public async Task ListScore() + { + var sql = @"SELECT * FROM +( +SELECT + IO.Id, + IO.`Code`, + IO.`Name`, + CASE WHEN LIODD.ScoreResult IS NULL THEN 1 ELSE 0 END IsNew, + LIODD.ScoreResult PrevScoreResult, + TIODD.Score, + TIODD.ScoreResult +FROM +( + SELECT + *, + (SELECT Id FROM bs_inspection_org_dir_stage WHERE Status = 1 ORDER BY CreatedTime DESC LIMIT 1) PrevStageId, + (SELECT Id FROM bs_inspection_org_dir_stage WHERE Status = 0) CurrentStageId + FROM + bs_inspection_org +) IO +LEFT JOIN bs_inspection_org_dir_detail LIODD ON IO.PrevStageId = LIODD.StageId AND IO.Id = LIODD.OrgId +LEFT JOIN bs_inspection_org_dir_detail TIODD ON IO.CurrentStageId = TIODD.StageId AND IO.Id = TIODD.OrgId +WHERE IO.Status = 1 +) T ORDER BY IsNew DESC, `Code`"; + return await _dapperRep.QueryAsync(sql); + } + + [HttpGet("/inspectionOrgDir/detail")] + public async Task Detail() + { + return await _dirStageRep.DetachedEntities.FirstOrDefaultAsync(p => p.Status.Equals(0)); + } + + private int ScoreResult(int score, int? prevScoreResult) + { + int result; + if (score >= 10) + { + result = 1; + } + else if (score >= 8) + { + result = 2; + } + else if (score >= 6) + { + result = 3; + } + else + { + result = 4; + } + + if (prevScoreResult.GetValueOrDefault(0).Equals(3) && result.Equals(3)) + { + result = 4; + } + + return result; + } + + [HttpPost("/inspectionOrgDir/saveScore")] + [UnitOfWork] + public async Task SaveScore([FromBody] InspectionOrgDirSaveInput input) + { + var stageId = input.Id; + + var isAdd = false; + // 新增修改名录主表 + if (String.IsNullOrEmpty(stageId)) + { + // 新增 + // 如果存在未公布的名录,则失败 + var isExistUnpublished = await _dirStageRep.DetachedEntities.AnyAsync(p => p.Status.Equals(0)); + if(isExistUnpublished) + throw Oops.Oh("存在未公布的名录,无法新增"); + + var isExistNo = await _dirStageRep.DetachedEntities.AnyAsync(p => p.No.Equals(input.No)); + if (isExistNo) + throw Oops.Oh("名录期号已存在"); + + var stage = input.Adapt(); + stageId = Guid.NewGuid().ToString().ToLower(); + stage.Id = stageId; + + await stage.InsertAsync(); + + isAdd = true; + } + else + { + var isExistNo = await _dirStageRep.DetachedEntities.AnyAsync(p => p.No.Equals(input.No) && !p.Id.Equals(input.Id)); + if (isExistNo) + throw Oops.Oh("名录期号已存在"); + + // 修改 + var stage = await _dirStageRep.FirstOrDefaultAsync(p => p.Id.Equals(input.Id)); + stage.No = input.No; + + await stage.UpdateAsync(); + } + + var prevStage = await _dirStageRep.DetachedEntities.OrderByDescending(p => p.CreatedTime).FirstOrDefaultAsync(p => !p.Id.Equals(stageId)); + var prevDetails = new List(); + if (prevStage != null) + { + prevDetails = await _dirDetailRep.DetachedEntities.Where(p => p.StageId.Equals(prevStage.Id)).ToListAsync(); + } + + // 新增修改名录详细表 + foreach (var detail in input.Detail) + { + var prevDetail = prevDetails.FirstOrDefault(p => p.OrgId.Equals(detail.OrgId)); + var scoreResult = ScoreResult(detail.Score, prevDetail?.ScoreResult); + + if (!isAdd) + { + // 名录已经存在时,判断每个机构的打分记录是否已经存在 + var _data = await _dirDetailRep.FirstOrDefaultAsync(p => p.OrgId.Equals(detail.OrgId) && p.StageId.Equals(stageId)); + if (_data != null) + { + _data.Score = detail.Score; + _data.ScoreResult = scoreResult; + await _data.UpdateAsync(); + continue; + } + } + + var data = detail.Adapt(); + data.StageId = stageId; + data.ScoreResult = scoreResult; + + await data.InsertAsync(); + } + } + + [HttpGet("/inspectionOrgDir/preview")] + public async Task Preview() + { + var stage = await _dirStageRep.FirstOrDefaultAsync(p => p.Status.Equals(0)); + if (stage == null) + { + // 当前没有需要发布的名录 + return null; + } + + var count = await _dirDetailRep.Where(p => p.StageId.Equals(stage.Id) && !p.ScoreResult.Equals(4)).CountAsync(); + + var sql = @"SELECT * FROM +( +SELECT + IO.Id, + IO.`Code`, + IO.`Name`, + IO.Address, + CASE + WHEN TIODD.ScoreResult = 4 THEN -1 + WHEN LIODD.ScoreResult IS NULL THEN 1 + ELSE 0 END `Status` +FROM +( + SELECT + *, + (SELECT Id FROM bs_inspection_org_dir_stage WHERE Status = 1 ORDER BY CreatedTime DESC LIMIT 1) PrevStageId, + (SELECT Id FROM bs_inspection_org_dir_stage WHERE Status = 0) CurrentStageId + FROM + bs_inspection_org +) IO +LEFT JOIN bs_inspection_org_dir_detail LIODD ON IO.PrevStageId = LIODD.StageId AND IO.Id = LIODD.OrgId +LEFT JOIN bs_inspection_org_dir_detail TIODD ON IO.CurrentStageId = TIODD.StageId AND IO.Id = TIODD.OrgId +WHERE (LIODD.ScoreResult IS NULL OR LIODD.ScoreResult < 4) AND IO.`Status` >= 0 +) T ORDER BY `Status` DESC, `Code`"; + var list = await _dapperRep.QueryAsync(sql); + + return new InspectionOrgDirPreviewOutput + { + Stage = stage, + Count = count, + List = list + }; + } + + [HttpPost("/inspectionOrgDir/publish")] + [UnitOfWork] + public async Task Publish() + { + var stage = await _dirStageRep.FirstOrDefaultAsync(p => p.Status.Equals(0)); + if (stage == null) + throw Oops.Oh("当前名录数据错误"); + + // 评分详情 + var details = await _dirDetailRep.DetachedEntities.Where(p => p.StageId.Equals(stage.Id)).ToListAsync(); + // 当前需打分的机构 + var orgIds = await _orgRep.DetachedEntities.Where(p => p.Status != InspectionOrgStatus.Delisting && p.Status != InspectionOrgStatus.Revoke).Select(p => p.Id).ToListAsync(); + if (orgIds.Any(p => !details.Select(p => p.OrgId).Contains(p))) + throw Oops.Oh("存在未评分的机构"); + + // 往期名录 + var prevStage = await _dirStageRep.DetachedEntities.OrderByDescending(p => p.CreatedTime).FirstOrDefaultAsync(p => !p.Id.Equals(stage.Id)); + // 往期机构 + var prevDetails = await _dirDetailRep.DetachedEntities.Where(p => p.StageId.Equals(prevStage.Id)).ToListAsync(); + // 往期黄牌机构 + var prevFilteredDetailOrgIds = new List(); + if (prevStage != null) + { + // 获取往期黄牌机构 + prevFilteredDetailOrgIds = prevDetails.Where(p => p.ScoreResult.Equals(3)).Select(p => p.OrgId).ToList(); + } + + // 对除名的机构修改状态 + var revmoeCount = 0; + var mayBeRemovedDetails = details.Where(p => p.ScoreResult.Equals(3) || p.ScoreResult.Equals(4)).ToList(); + foreach (var detail in mayBeRemovedDetails) + { + if ((detail.ScoreResult.Equals(3) && prevFilteredDetailOrgIds.Contains(detail.OrgId)) + || detail.ScoreResult.Equals(4)) + { + // 本期及往期为黄牌或者本期为红牌的,进行除名 + var org = await _orgRep.FirstOrDefaultAsync(p => p.Id.Equals(detail.OrgId)); + org.Status = InspectionOrgStatus.Delisting; + await org.UpdateIncludeAsync(new[] { nameof(BsInspectionOrg.Status) }); + revmoeCount++; + } + } + + // 新增机构数量 + var newDetailsCount = details.Where(p => (p.ScoreResult.Equals(1) || p.ScoreResult.Equals(2)) && !prevDetails.Select(p => p.OrgId).Contains(p.OrgId)).Count(); + + // 总数减去除名 + stage.OrgAmount = details.Count - revmoeCount; + // 除名 + stage.RemovedOrgAmount = revmoeCount; + // 新增 + stage.NewOrgAmount = newDetailsCount; + + stage.Status = 1; + stage.PublishTime = DateTime.Now; + await stage.UpdateAsync(); + } + } +} diff --git a/Api/Ewide.Application/Startup.cs b/Api/Ewide.Application/Startup.cs new file mode 100644 index 0000000..ade1ce8 --- /dev/null +++ b/Api/Ewide.Application/Startup.cs @@ -0,0 +1,30 @@ +using Furion; +using Microsoft.Extensions.DependencyInjection; +using System.Linq; + +namespace Ewide.Application +{ + public class Startup : AppStartup + { + public void ConfigureServices(IServiceCollection services) + { + //services.AddSqlSugar(new ConnectionConfig + //{ + // ConnectionString = App.Configuration["ConnectionStrings:DefaultConnection"], + // DbType = DbType.Sqlite, + // IsAutoCloseConnection = true, + // InitKeyType = InitKeyType.Attribute + //}, + //db => + //{ + // // db.DbMaintenance.CreateDatabase(); + // // db.CodeFirst.SetStringDefaultLength(200).InitTables(typeof(Test)); + + // db.Aop.OnLogExecuting = (sql, pars) => + // { + // App.PrintToMiniProfiler("SqlSugar", "Info", sql + "\r\n" + string.Join(",", pars?.Select(it => it.ParameterName + ":" + it.Value))); + // }; + //}); + } + } +} diff --git a/Api/Ewide.Application/applicationsettings.json b/Api/Ewide.Application/applicationsettings.json new file mode 100644 index 0000000..29091fa --- /dev/null +++ b/Api/Ewide.Application/applicationsettings.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/Api/HouseSafety.sln b/Api/HouseSafety.sln new file mode 100644 index 0000000..6114a40 --- /dev/null +++ b/Api/HouseSafety.sln @@ -0,0 +1,100 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31424.327 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ewide.Application", "Ewide.Application\Ewide.Application.csproj", "{68773592-648C-487D-B68B-33E017A37EC6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ewide.Core", "..\EwideCore\Api\Ewide.Core\Ewide.Core.csproj", "{07C08564-894E-43CE-9981-FE78E2BE3BE0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ewide.Database.Migrations", "..\EwideCore\Api\Ewide.Database.Migrations\Ewide.Database.Migrations.csproj", "{ADFE4B2B-B396-4A19-85D5-AACD6530D94D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ewide.EntityFramework.Core", "..\EwideCore\Api\Ewide.EntityFramework.Core\Ewide.EntityFramework.Core.csproj", "{D2F59ABE-62DE-4C5C-A104-4A2275ECD9CB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ewide.Test", "..\EwideCore\Api\Ewide.Test\Ewide.Test.csproj", "{27696F6B-F895-4A7B-B47C-777D83259DAA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ewide.Web.Core", "..\EwideCore\Api\Ewide.Web.Core\Ewide.Web.Core.csproj", "{9BF809F4-3219-4B03-8DD9-E7B174DA2041}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ewide.Web.Entry", "..\EwideCore\Api\Ewide.Web.Entry\Ewide.Web.Entry.csproj", "{D5D2CF2B-BD26-4C40-930A-CCBC30479520}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Furion", "Furion", "{B77F1834-B24B-439F-B828-8621138C560F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Furion", "..\EwideCore\Api\Furion\framework\Furion\Furion.csproj", "{E9570EF7-C1D5-4C4E-8FC9-26AB598FF2A2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Furion.Extras.Authentication.JwtBearer", "..\EwideCore\Api\Furion\framework\Furion.Extras.Authentication.JwtBearer\Furion.Extras.Authentication.JwtBearer.csproj", "{8AB345D4-0EEC-4017-BED4-0DFFF696D8B3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Furion.Extras.DatabaseAccessor.Dapper", "..\EwideCore\Api\Furion\framework\Furion.Extras.DatabaseAccessor.Dapper\Furion.Extras.DatabaseAccessor.Dapper.csproj", "{EC8E2F6A-87C9-4369-8E00-7AE74BBA53C7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Furion.Extras.Logging.Serilog", "..\EwideCore\Api\Furion\framework\Furion.Extras.Logging.Serilog\Furion.Extras.Logging.Serilog.csproj", "{EF6EF253-94D0-4143-A763-3813922F43A2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Furion.Extras.ObjectMapper.Mapster", "..\EwideCore\Api\Furion\framework\Furion.Extras.ObjectMapper.Mapster\Furion.Extras.ObjectMapper.Mapster.csproj", "{35C3B1E9-1291-4B54-99C7-0849FB02569B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {68773592-648C-487D-B68B-33E017A37EC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68773592-648C-487D-B68B-33E017A37EC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68773592-648C-487D-B68B-33E017A37EC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68773592-648C-487D-B68B-33E017A37EC6}.Release|Any CPU.Build.0 = Release|Any CPU + {07C08564-894E-43CE-9981-FE78E2BE3BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07C08564-894E-43CE-9981-FE78E2BE3BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07C08564-894E-43CE-9981-FE78E2BE3BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07C08564-894E-43CE-9981-FE78E2BE3BE0}.Release|Any CPU.Build.0 = Release|Any CPU + {ADFE4B2B-B396-4A19-85D5-AACD6530D94D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADFE4B2B-B396-4A19-85D5-AACD6530D94D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADFE4B2B-B396-4A19-85D5-AACD6530D94D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADFE4B2B-B396-4A19-85D5-AACD6530D94D}.Release|Any CPU.Build.0 = Release|Any CPU + {D2F59ABE-62DE-4C5C-A104-4A2275ECD9CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D2F59ABE-62DE-4C5C-A104-4A2275ECD9CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2F59ABE-62DE-4C5C-A104-4A2275ECD9CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D2F59ABE-62DE-4C5C-A104-4A2275ECD9CB}.Release|Any CPU.Build.0 = Release|Any CPU + {27696F6B-F895-4A7B-B47C-777D83259DAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27696F6B-F895-4A7B-B47C-777D83259DAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27696F6B-F895-4A7B-B47C-777D83259DAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27696F6B-F895-4A7B-B47C-777D83259DAA}.Release|Any CPU.Build.0 = Release|Any CPU + {9BF809F4-3219-4B03-8DD9-E7B174DA2041}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BF809F4-3219-4B03-8DD9-E7B174DA2041}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BF809F4-3219-4B03-8DD9-E7B174DA2041}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BF809F4-3219-4B03-8DD9-E7B174DA2041}.Release|Any CPU.Build.0 = Release|Any CPU + {D5D2CF2B-BD26-4C40-930A-CCBC30479520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5D2CF2B-BD26-4C40-930A-CCBC30479520}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5D2CF2B-BD26-4C40-930A-CCBC30479520}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5D2CF2B-BD26-4C40-930A-CCBC30479520}.Release|Any CPU.Build.0 = Release|Any CPU + {E9570EF7-C1D5-4C4E-8FC9-26AB598FF2A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9570EF7-C1D5-4C4E-8FC9-26AB598FF2A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9570EF7-C1D5-4C4E-8FC9-26AB598FF2A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9570EF7-C1D5-4C4E-8FC9-26AB598FF2A2}.Release|Any CPU.Build.0 = Release|Any CPU + {8AB345D4-0EEC-4017-BED4-0DFFF696D8B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8AB345D4-0EEC-4017-BED4-0DFFF696D8B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8AB345D4-0EEC-4017-BED4-0DFFF696D8B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8AB345D4-0EEC-4017-BED4-0DFFF696D8B3}.Release|Any CPU.Build.0 = Release|Any CPU + {EC8E2F6A-87C9-4369-8E00-7AE74BBA53C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC8E2F6A-87C9-4369-8E00-7AE74BBA53C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC8E2F6A-87C9-4369-8E00-7AE74BBA53C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC8E2F6A-87C9-4369-8E00-7AE74BBA53C7}.Release|Any CPU.Build.0 = Release|Any CPU + {EF6EF253-94D0-4143-A763-3813922F43A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF6EF253-94D0-4143-A763-3813922F43A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF6EF253-94D0-4143-A763-3813922F43A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF6EF253-94D0-4143-A763-3813922F43A2}.Release|Any CPU.Build.0 = Release|Any CPU + {35C3B1E9-1291-4B54-99C7-0849FB02569B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35C3B1E9-1291-4B54-99C7-0849FB02569B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35C3B1E9-1291-4B54-99C7-0849FB02569B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35C3B1E9-1291-4B54-99C7-0849FB02569B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E9570EF7-C1D5-4C4E-8FC9-26AB598FF2A2} = {B77F1834-B24B-439F-B828-8621138C560F} + {8AB345D4-0EEC-4017-BED4-0DFFF696D8B3} = {B77F1834-B24B-439F-B828-8621138C560F} + {EC8E2F6A-87C9-4369-8E00-7AE74BBA53C7} = {B77F1834-B24B-439F-B828-8621138C560F} + {EF6EF253-94D0-4143-A763-3813922F43A2} = {B77F1834-B24B-439F-B828-8621138C560F} + {35C3B1E9-1291-4B54-99C7-0849FB02569B} = {B77F1834-B24B-439F-B828-8621138C560F} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {ED14FD8E-FA4B-477E-A71E-E4F21A9670B1} + EndGlobalSection +EndGlobal diff --git a/EwideCore b/EwideCore new file mode 160000 index 0000000..9f81b83 --- /dev/null +++ b/EwideCore @@ -0,0 +1 @@ +Subproject commit 9f81b838ceba17846d32ba3a77e19c24b81baa0c diff --git a/Web/.env b/Web/.env new file mode 100644 index 0000000..4f79a0f --- /dev/null +++ b/Web/.env @@ -0,0 +1 @@ +GENERATE_SOURCEMAP=false \ No newline at end of file diff --git a/Web/.env.development b/Web/.env.development new file mode 100644 index 0000000..b8402ce --- /dev/null +++ b/Web/.env.development @@ -0,0 +1 @@ +REACT_APP_BASE_URL=http://localhost:5566/ \ No newline at end of file diff --git a/Web/.env.production b/Web/.env.production new file mode 100644 index 0000000..a7692b7 --- /dev/null +++ b/Web/.env.production @@ -0,0 +1 @@ +REACT_APP_BASE_URL=http://118.178.224.202:90/ \ No newline at end of file diff --git a/Web/.gitignore b/Web/.gitignore new file mode 100644 index 0000000..4d29575 --- /dev/null +++ b/Web/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/Web/.prettierrc.js b/Web/.prettierrc.js new file mode 100644 index 0000000..86e4998 --- /dev/null +++ b/Web/.prettierrc.js @@ -0,0 +1,24 @@ +module.exports = { + "extends": [ + "airbnb", + "prettier", + "prettier/react" + ], + "printWidth": 100, // 超过最大值换行 + "tabWidth": 4, // 缩进字节数 + "useTabs": false, // 缩进不使用tab,使用空格 + "semi": false, // 句尾添加分号 + "singleQuote": true, // 使用单引号代替双引号 + "proseWrap": "preserve", // 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行 + "arrowParens": "avoid", // (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 + "bracketSpacing": true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }" + "disableLanguages": ["vue"], // 不格式化vue文件,vue文件的格式化单独设置 + "endOfLine": "auto", // 结尾是 \n \r \n\r auto + "eslintIntegration": true, //是否让prettier使用eslint的代码格式进行校验 + "htmlWhitespaceSensitivity": "ignore", + "ignorePath": ".prettierignore", // 不使用prettier格式化的文件填写在项目的.prettierignore文件中 + "jsxBracketSameLine": false, // 在jsx中把'>' 单独放一行 + "jsxSingleQuote": false, // 在jsx中使用单引号代替双引号 + "trailingComma": "es5", // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号) + "tslintIntegration": false // 不让prettier使用tslint的代码格式进行校验 +} \ No newline at end of file diff --git a/Web/README.md b/Web/README.md new file mode 100644 index 0000000..02aac3f --- /dev/null +++ b/Web/README.md @@ -0,0 +1,70 @@ +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `yarn start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.\ +You will also see any lint errors in the console. + +### `yarn test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `yarn build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `yarn eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) + +### Analyzing the Bundle Size + +This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) + +### Making a Progressive Web App + +This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) + +### Advanced Configuration + +This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) + +### Deployment + +This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) + +### `yarn build` fails to minify + +This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/Web/craco.config.js b/Web/craco.config.js new file mode 100644 index 0000000..e240718 --- /dev/null +++ b/Web/craco.config.js @@ -0,0 +1,35 @@ +const CracoLessPlugin = require('craco-less') +const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin') + +module.exports = { + devServer: { + open: true, + port: 6591, + proxy: { + '/api': { + target: 'http://localhost:5566', + pathRewrite: { + '^/api': '' + } + } + } + }, + plugins: [ + { + plugin: CracoLessPlugin, + options: { + lessLoaderOptions: { + lessOptions: { + javascriptEnabled: true, + }, + }, + importLoaders: 2 + }, + }, + ], + webpack: { + plugins: [ + new MonacoWebpackPlugin() + ] + }, +} \ No newline at end of file diff --git a/Web/jsconfig.json b/Web/jsconfig.json new file mode 100644 index 0000000..ec9aa3f --- /dev/null +++ b/Web/jsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "baseUrl": "src" + }, + "include": ["src"] +} \ No newline at end of file diff --git a/Web/package.json b/Web/package.json new file mode 100644 index 0000000..e486365 --- /dev/null +++ b/Web/package.json @@ -0,0 +1,67 @@ +{ + "name": "web-react", + "version": "0.1.0", + "private": true, + "dependencies": { + "@craco/craco": "^6.1.2", + "@testing-library/jest-dom": "^5.11.4", + "@testing-library/react": "^11.1.0", + "@testing-library/user-event": "^12.1.10", + "antd": "^4.16.2", + "axios": "^0.21.1", + "braft-editor": "^2.3.9", + "craco-less": "^1.17.1", + "crypto-js": "^4.0.0", + "echarts": "^5.1.2", + "jsencrypt": "^3.2.0", + "lodash": "^4.17.21", + "monaco-editor": "^0.25.1", + "monaco-editor-webpack-plugin": "^4.0.0", + "nprogress": "^0.2.0", + "photoswipe": "^4.1.3", + "react": "^17.0.2", + "react-color": "^2.19.3", + "react-cropper": "^2.1.8", + "react-dom": "^17.0.2", + "react-infinite-scroller": "^1.2.4", + "react-json-view": "^1.21.3", + "react-monaco-editor": "^0.43.0", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", + "react-scripts": "4.0.3", + "redux": "^4.1.0", + "swiper": "^6.7.0", + "web-vitals": "^1.0.1" + }, + "scripts": { + "start": "craco start", + "build": "craco build", + "test": "craco test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ], + "rules": { + "eqeqeq": "off", + "no-unused-vars": "off", + "no-sparse-arrays": "off", + "array-callback-return": "off", + "jsx-a11y/anchor-is-valid": "off" + } + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/Web/public/doc-code/api/setting.js b/Web/public/doc-code/api/setting.js new file mode 100644 index 0000000..5b2740e --- /dev/null +++ b/Web/public/doc-code/api/setting.js @@ -0,0 +1,14 @@ +export default { + /* 自定义的接口名称 */ + apiName: [ + /* 接口地址 */ + url, + /* 请求类型 [get | post] */ + 'get', + /* axios所需的设置参数 */ + options, + ], + + /* 默认为get接口 */ + apiPostName: getUrl +} \ No newline at end of file diff --git a/Web/public/doc-code/api/usage.js b/Web/public/doc-code/api/usage.js new file mode 100644 index 0000000..f2bc671 --- /dev/null +++ b/Web/public/doc-code/api/usage.js @@ -0,0 +1,25 @@ +import { api } from 'common/api' + +api.apiName(params) + .then(res => { + /* ... */ + }) + .catch(error => { + /* catch */ + }) + .finally(() => { + /* finally */ + }) + + +// 或者采用异步 +async function foo() { + try { + const res = await api.apiName(params) + /* ... */ + } catch (error) { + /* catch */ + } finally { + /* finally */ + } +} \ No newline at end of file diff --git a/Web/public/doc-code/application/dto.cs b/Web/public/doc-code/application/dto.cs new file mode 100644 index 0000000..f2faad3 --- /dev/null +++ b/Web/public/doc-code/application/dto.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; + +namespace Ewide.Application +{ + // 继承PageInputBase,可以直接使用一些通用的查询和分页字段 + public class DtoPageInput : Core.PageInputBase {} + + // 可定义一个主键Dto + public class DtoKeyInput + { + public virtual string Id { get; set; } + } + + // 可定义一个必传主键的Dto + public class DtoKeyRequiredInput : DtoKeyInput + { + [Required] + public override string Id { get; set; } + } + + public class DtoAddInput + { + [MaxLength(100)] + [Required] + public string RequiredString { get; set; } + } + + public class DtoUpdateInput : DtoAddInput + { + [Required] + public override string Id { get; set; } + } + + public class DtoDeleteInput: DtoKeyInput {} +} \ No newline at end of file diff --git a/Web/public/doc-code/application/entity.cs b/Web/public/doc-code/application/entity.cs new file mode 100644 index 0000000..3503f31 --- /dev/null +++ b/Web/public/doc-code/application/entity.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ewide.Application +{ + // Table特性设定表在数据库中的表名 + [Table("bs_table_name")] + [Comment("表名")] + // 这里继承Core.DEntityBase,会自动添加Id及一些常用字段 + public class BsTableName : Core.DEntityBase + { + // Comment特性用于生成字段说明 + [Comment("字符字段")] + // MaxLength特性用于限定字段值长度,可以不设置 + [MaxLength(50)] + // Required特性设置字段是否不为空 + [Required] + public string StringField { get; set; } + + [Comment("整形字段")] + [MaxLength(3)] + [Required] + public int IntField { get; set; } + } +} diff --git a/Web/public/doc-code/application/interface.cs b/Web/public/doc-code/application/interface.cs new file mode 100644 index 0000000..388e010 --- /dev/null +++ b/Web/public/doc-code/application/interface.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; + +namespace Ewide.Application.Service +{ + public interface Interface + { + Task Page(DtoPageInput input); + + Task Add(DtoAddInput input); + + Task Update(DtoUpdateInput input); + + Task Delete(DtoDeleteInput input); + } +} \ No newline at end of file diff --git a/Web/public/doc-code/application/service.cs b/Web/public/doc-code/application/service.cs new file mode 100644 index 0000000..d821ee8 --- /dev/null +++ b/Web/public/doc-code/application/service.cs @@ -0,0 +1,90 @@ +using Dapper; +using Ewide.Core; +using Ewide.Core.Extension; +using Furion.DatabaseAccessor; +using Furion.DependencyInjection; +using Furion.DynamicApiController; + +namespace Ewide.Application.Service +{ + [ApiDescriptionSettings(Name = "ServiceDoc")] + public class Service : Interface, IDynamicApiController, ITransient + { + // Dapper仓储 + private readonly IDapperRepository _dapperRep; + + // 用户信息 + private readonly IUserManager _userManager; + + // 数据(实体Entity)仓储 + private readonly IRepository _entityRep; + + public Service( + IDapperRepository dapperRep, + + IUserManager userManager, + + IRepository entityRep + ) + { + _dapperRep = dapperRep; + + _userManager = userManager; + + _entityRep = entityRep; + } + + /// + /// 查询 - EF方式 + /// + /// + /// + public async Task Page(DtoPageInput input) + { + var data = await _entityRep.DetachedEntities.ToPageData(input); + return PageDataResult.PageResult(data); + } + + /// + /// 查询 - Dapper方式 + /// + /// + /// + public async Task Page(DtoPageInput input) + { + var sql = "..."; + var data = await _dapperRep.QueryPageDataDynamic(sql, input); + return data; + } + + /// + /// 新增 + /// + /// + /// + public async Task Add(DtoAddInput input) + { + + } + + /// + /// 编辑 + /// + /// + /// + public async Task Update(DtoUpdateInput input) + { + + } + + /// + /// 删除 + /// + /// + /// + public async Task Delete(DtoDeleteInput input) + { + + } + } +} \ No newline at end of file diff --git a/Web/public/doc-code/auth/index.txt b/Web/public/doc-code/auth/index.txt new file mode 100644 index 0000000..5e10e0b --- /dev/null +++ b/Web/public/doc-code/auth/index.txt @@ -0,0 +1,51 @@ +import { Auth } from 'components' +import auth from 'components/authorized/handler' + +/** + * 简单的权限标识 + */ +function foo1() { + return ( + + 连接 + + ) +} + +/** + * 多个并且关系的权限标识 + */ +function foo2() { + return ( + + 连接 + + ) +} + +/** + * 多个或者关系的权限标识 + */ +function foo3() { + return ( + + 连接 + + ) +} + +/** + * 前缀简化 + */ +function foo4() { + return ( + + 连接 + + ) +} + +/** + * 纯js + */ +const flag = auth('permissions:name') // => Boolean diff --git a/Web/public/doc-code/util/dic/index.js b/Web/public/doc-code/util/dic/index.js new file mode 100644 index 0000000..da683c1 --- /dev/null +++ b/Web/public/doc-code/util/dic/index.js @@ -0,0 +1,10 @@ +import getDictData from 'util/dic' + +async function foo() { + const code = await getDictData('dic_code_one', 'dic_code_two') + // => + // code = { + // dicCodeOne: [], + // dicCodeTwo: [], + // } +} \ No newline at end of file diff --git a/Web/public/doc-code/util/query/index.js b/Web/public/doc-code/util/query/index.js new file mode 100644 index 0000000..24bffbc --- /dev/null +++ b/Web/public/doc-code/util/query/index.js @@ -0,0 +1,32 @@ +import { QueryType, getSearchDateRange, getSearchInfo } from 'util/query' + +getSearchInfo({ + query: { + value: '123', + text: '123', + code: 'abc', + check: ['1', '2', '3'], + range: [1, 10], + dateRange: getSearchDateRange(['2021-01-01', '2021-01-10']) + }, + queryType: { + text: QueryType.Equal, + code: QueryType.Like, + check: QueryType.Equal, + range: [QueryType.GreaterThanOrEqual, QueryType.LessThan], + dateRange: [QueryType.GreaterThanOrEqual, QueryType.LessThan] + } +}) + +// => + +[ + { field: 'value', value: ['123'] }, + { field: 'text', value: ['123'], type: '=' }, + { field: 'code', value: ['abc'], type: 'like' }, + { field: 'check', value: ['1', '2', '3'], type: '=' }, + { field: 'range', value: [1], type: '>=' }, + { field: 'range', value: [10], type: '<' }, + { field: 'dateRange', value: ['2021-01-01'], type: '>=' }, + { field: 'dateRange', value: ['2021-01-11'], type: '<' } +] \ No newline at end of file diff --git a/Web/public/favicon.ico b/Web/public/favicon.ico new file mode 100644 index 0000000..ae7c797 Binary files /dev/null and b/Web/public/favicon.ico differ diff --git a/Web/public/index.html b/Web/public/index.html new file mode 100644 index 0000000..fb47694 --- /dev/null +++ b/Web/public/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + 宽易科技 + + + + +
+ + + diff --git a/Web/public/logo192.png b/Web/public/logo192.png new file mode 100644 index 0000000..fc44b0a Binary files /dev/null and b/Web/public/logo192.png differ diff --git a/Web/public/logo512.png b/Web/public/logo512.png new file mode 100644 index 0000000..a4e47a6 Binary files /dev/null and b/Web/public/logo512.png differ diff --git a/Web/public/manifest.json b/Web/public/manifest.json new file mode 100644 index 0000000..080d6c7 --- /dev/null +++ b/Web/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/Web/public/robots.txt b/Web/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/Web/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/Web/seed/form-tabs/index.jsx b/Web/seed/form-tabs/index.jsx new file mode 100644 index 0000000..470a4a0 --- /dev/null +++ b/Web/seed/form-tabs/index.jsx @@ -0,0 +1,153 @@ +import React, { Component } from 'react' +import { Button, Tabs } from 'antd' +import { ComponentDynamic, Container } from 'components' +import { isEqual, merge } from 'lodash' + +const tabs = [ + { + title: '标题', + component: () => import('./tab'), + show: true, + }, +] + +export default class index extends Component { + state = { + actived: '0', + loading: true, + record: null, + saveDisabled: true, + saving: false, + } + + // 子表单实例集合 + children = [] + + // 整合提交数据 + formData = {} + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * DOM加载完成钩子,可在此获取详细数据赋值到record + */ + componentDidMount() {} + + /** + * 接收到所有子组件已加载完成,并启用提交按钮 + */ + call(child, index) { + this.children[index] = child + if (this.children.filter(p => p).length === tabs.filter(p => p.show).length) { + this.setState({ saveDisabled: false }) + } + } + + async onSubmit() { + for (const child of this.children) { + try { + const data = await child.getData() + merge(this.formData, data) + } catch (e) { + return e + } + } + + //#region 提交数据 + this.setState({ saving: true }) + this.setState({ saving: false }) + //#endregion + } + + render() { + const { id } = this.props + + const { actived, loading, record, saveDisabled, saving } = this.state + + return ( +
+
+ {/* 底部工具栏(需放在前面) */} +
+ +
+ + + + + +
+
+
+ {/* 顶部信息栏,不需要时刻删除 */} +
+
+ { + this.setState({ actived: activeKey }) + }} + > + {tabs.map( + (tab, i) => + tab.show && ( + + ) + )} + +
+ {tabs.map((tab, i) => { + if (tab.show) { + return ( +
+ this.call(child, i)} + /> +
+ ) + } + return <> + })} +
+
+
+
+ ) + } +} diff --git a/Web/seed/form-tabs/tab/index.jsx b/Web/seed/form-tabs/tab/index.jsx new file mode 100644 index 0000000..3d2912d --- /dev/null +++ b/Web/seed/form-tabs/tab/index.jsx @@ -0,0 +1,126 @@ +import React, { Component } from 'react' +import ReactDOM from 'react-dom' +import { Anchor, Card, Col, Divider, Row, Spin } from 'antd' +import { AntIcon, ComponentDynamic, Container } from 'components' +import { isEqual, merge } from 'lodash' + +const parts = [ + { + // title: '标题', + component: () => import('./part'), + }, +] + +export default class index extends Component { + // 子表单实例集合 + children = [] + + // 整合提交数据 + formData = {} + + // 锚点挂载DOM + container = window + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) || this.props.loading !== props.loading + } + + /** + * 加载完成,通知父级组件并传递自身 + */ + call(child, index) { + this.children[index] = child + if (this.children.filter(p => p).length === parts.length) { + const { onRef } = this.props + if (onRef) onRef(this) + } + } + + /** + * 从下级组件获取表单数据,并传递给更上级组件 + * [异步,必要] + * @returns + */ + async getData() { + for (const child of this.children) { + const data = await child.getData() + merge(this.formData, data) + } + return this.formData + } + + /** + * 设置锚点容器 + * [非必要] + * @param {*} container + */ + setContainer = container => { + this.container = (ReactDOM.findDOMNode(container) || {}).parentNode + } + + /** + * 渲染 + * 当前渲染结构已完善,非必要可以不用修改 + * [必要] + * @returns + */ + render() { + const { id, loading } = this.props + + return ( + + + +
+
+ + {parts.map((item, i) => ( + +
+ {item.title &&
{item.title}
} + } + wrapperClassName={loading && 'h-400-min'} + > + {!loading && ( + this.call(child, i)} + /> + )} + +
+ {i < parts.length - 1 && } +
+ ))} +
+ + {/* 锚点,如果不需要可以删除以下节点 */} + + this.container} + offsetTop={24} + targetOffset={100} + wrapperStyle={{ backgroundColor: 'transparent' }} + onClick={e => e.preventDefault()} + > + {parts.map((part, i) => ( + + ))} + + + + + ) + } +} diff --git a/Web/seed/form-tabs/tab/part.jsx b/Web/seed/form-tabs/tab/part.jsx new file mode 100644 index 0000000..9b70bef --- /dev/null +++ b/Web/seed/form-tabs/tab/part.jsx @@ -0,0 +1,115 @@ +import React, { Component } from 'react' +import { Form, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, isEqual } from 'lodash' + +const initialValues = {} + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +export default class part extends Component { + state = { + loading: true, + codes: {}, + options: {}, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * DOM加载完成钩子,绑定数据 + */ + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + /** + * 加载完成,通知父级组件并传递自身 + */ + call() { + const { onRef } = this.props + if (onRef) onRef(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + /** + * 表单change事件处理,包括了所有字段的change + * [异步,非必要] + * @param {*} changedValues + * @param {*} allValues + */ + async onValuesChange(changedValues, allValues) {} + //#endregion + + render() { + const { loading } = this.state + + return ( + }> +
+ this.onValuesChange(changedValues, allValues) + } + >
+
+ ) + } +} diff --git a/Web/seed/form/index.jsx b/Web/seed/form/index.jsx new file mode 100644 index 0000000..eb193ee --- /dev/null +++ b/Web/seed/form/index.jsx @@ -0,0 +1,169 @@ +import React, { Component } from 'react' +import ReactDOM from 'react-dom' +import { Anchor, Button, Card, Col, Divider, Row, Spin } from 'antd' +import { AntIcon, ComponentDynamic, Container } from 'components' +import { isEqual, merge } from 'lodash' + +const parts = [ + { + // title: '标题', + component: () => import('./part'), + }, +] + +export default class index extends Component { + state = { + loading: true, + record: null, + saveDisabled: true, + saving: false, + } + + // 子表单实例集合 + children = [] + + // 整合提交数据 + formData = {} + + // 锚点挂载DOM + container = window + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * DOM加载完成钩子,可在此获取详细数据赋值到record + */ + componentDidMount() {} + + call(child, index) { + this.children[index] = child + if (this.children.filter(p => p).length === parts.filter(p => p.show).length) { + this.setState({ saveDisabled: false }) + } + } + + /** + * 提交 + * [异步,必要] + * @returns + */ + async onSubmit() { + for (const child of this.children) { + try { + const data = await child.getData() + merge(this.formData, data) + } catch (e) { + return e + } + } + + //#region 提交数据 + this.setState({ saving: true }) + this.setState({ saving: false }) + //#endregion + } + + /** + * 设置锚点容器 + * [非必要] + * @param {*} container + */ + setContainer = container => { + this.container = (ReactDOM.findDOMNode(container) || {}).parentNode + } + + /** + * 渲染 + * 当前渲染结构已完善,非必要可以不用修改 + * [必要] + * @returns + */ + render() { + const { id } = this.props + + const { loading, record, saveDisabled, saving } = this.state + + return ( +
+ + + +
+
+ + {parts.map((item, i) => ( + +
+ {item.title &&
{item.title}
} + } + wrapperClassName={loading && 'h-400-min'} + > + {!loading && ( + this.call(child, i)} + /> + )} + +
+ {i < parts.length - 1 && } +
+ ))} +
+ + {/* 锚点,如果不需要可以删除以下节点 */} + + this.container} + offsetTop={24} + targetOffset={100} + wrapperStyle={{ backgroundColor: 'transparent' }} + onClick={e => e.preventDefault()} + > + {parts.map((part, i) => ( + + ))} + + + + +
+ +
+ + + + + +
+
+
+
+ ) + } +} diff --git a/Web/seed/form/part.jsx b/Web/seed/form/part.jsx new file mode 100644 index 0000000..9b70bef --- /dev/null +++ b/Web/seed/form/part.jsx @@ -0,0 +1,115 @@ +import React, { Component } from 'react' +import { Form, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, isEqual } from 'lodash' + +const initialValues = {} + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +export default class part extends Component { + state = { + loading: true, + codes: {}, + options: {}, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * DOM加载完成钩子,绑定数据 + */ + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + /** + * 加载完成,通知父级组件并传递自身 + */ + call() { + const { onRef } = this.props + if (onRef) onRef(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + /** + * 表单change事件处理,包括了所有字段的change + * [异步,非必要] + * @param {*} changedValues + * @param {*} allValues + */ + async onValuesChange(changedValues, allValues) {} + //#endregion + + render() { + const { loading } = this.state + + return ( + }> +
+ this.onValuesChange(changedValues, allValues) + } + >
+
+ ) + } +} diff --git a/Web/seed/query-table-form/form.jsx b/Web/seed/query-table-form/form.jsx new file mode 100644 index 0000000..cebd795 --- /dev/null +++ b/Web/seed/query-table-form/form.jsx @@ -0,0 +1,78 @@ +import React, { Component } from 'react' +import { Form, Spin } from 'antd' +import { AntIcon } from 'components' +import { api } from 'common/api' + +const initialValues = {} + +export default class form extends Component { + state = { + // 加载状态 + loading: true, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + const state = { loading: false } + //#region 从后端转换成前段所需格式,也可以在此处调用获取详细数据接口 + if (params.id) { + this.record = (await api).data + } + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState(state) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + //#endregion + + render() { + return ( +
+ }> +
+
+
+ ) + } +} diff --git a/Web/seed/query-table/index.jsx b/Web/seed/query-table/index.jsx new file mode 100644 index 0000000..95eb779 --- /dev/null +++ b/Web/seed/query-table/index.jsx @@ -0,0 +1,235 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, message as Message, Popconfirm } from 'antd' +import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import FormBody from './form' + +/** + * 注释段[\/**\/]为必须要改 + */ + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api /**/, + add: api /**/, + edit: api /**/, + delete: api /**/, +} + +/** + * 用于弹窗标题 + * [必要] + */ +const name = '/**/' + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = '/**/' + +export default class index extends Component { + state = { + codes: {}, + } + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + columns = [] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ [authName]: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, { id }) => ( + + + this.onOpen(this.editForm, id)}>编辑 + + + this.onDelete(id)} + > + 删除 + + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData(/**/).then(codes => { + this.setState({ codes }, () => { + onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id) { + modal.current.open({ id }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + /** + * 删除 + * @param {*} id + */ + onDelete(id) { + this.onAction(apiAction.delete({ id }), '删除成功') + } + + //#region 自定义方法 + //#endregion + + render() { + return ( + +
+ + } + operator={ + + + + } + /> + + + + this.table.current.onReloadData()} + > + + + + + + this.table.current.onReloadData()} + > + + + +
+ ) + } +} diff --git a/Web/src/App.js b/Web/src/App.js new file mode 100644 index 0000000..77a1d96 --- /dev/null +++ b/Web/src/App.js @@ -0,0 +1,16 @@ +import React from 'react' +import { ConfigProvider } from 'antd' +import zhCN from 'antd/lib/locale/zh_CN' +import moment from 'moment' +import 'moment/locale/zh-cn' + +moment.locale('zh-cn') + +const App = () => ( +
+ + +
+); + +export default App \ No newline at end of file diff --git a/Web/src/assets/image/adorn/house-top-01.png b/Web/src/assets/image/adorn/house-top-01.png new file mode 100644 index 0000000..fbaee95 Binary files /dev/null and b/Web/src/assets/image/adorn/house-top-01.png differ diff --git a/Web/src/assets/image/login-bg-00.jpg b/Web/src/assets/image/login-bg-00.jpg new file mode 100644 index 0000000..2941d97 Binary files /dev/null and b/Web/src/assets/image/login-bg-00.jpg differ diff --git a/Web/src/assets/image/login-bg-01.jpg b/Web/src/assets/image/login-bg-01.jpg new file mode 100644 index 0000000..3ae5f88 Binary files /dev/null and b/Web/src/assets/image/login-bg-01.jpg differ diff --git a/Web/src/assets/image/login-bg-02.jpg b/Web/src/assets/image/login-bg-02.jpg new file mode 100644 index 0000000..8ffb240 Binary files /dev/null and b/Web/src/assets/image/login-bg-02.jpg differ diff --git a/Web/src/assets/image/login-bg-03.jpg b/Web/src/assets/image/login-bg-03.jpg new file mode 100644 index 0000000..b0cdfe8 Binary files /dev/null and b/Web/src/assets/image/login-bg-03.jpg differ diff --git a/Web/src/assets/image/logo-w.png b/Web/src/assets/image/logo-w.png new file mode 100644 index 0000000..a0e7b80 Binary files /dev/null and b/Web/src/assets/image/logo-w.png differ diff --git a/Web/src/assets/image/logo.png b/Web/src/assets/image/logo.png new file mode 100644 index 0000000..8625b51 Binary files /dev/null and b/Web/src/assets/image/logo.png differ diff --git a/Web/src/assets/style/dark/extend.less b/Web/src/assets/style/dark/extend.less new file mode 100644 index 0000000..7f3c2c9 --- /dev/null +++ b/Web/src/assets/style/dark/extend.less @@ -0,0 +1,11 @@ +@import '~antd/dist/antd.dark.less'; +@padding-xxs: 4px; +@padding-xl: 32px; +body { + line-height: 1.42857143; +} +#root { + transition: @animation-duration-slow opacity; + + opacity: 1 !important; +} diff --git a/Web/src/assets/style/dark/index.less b/Web/src/assets/style/dark/index.less new file mode 100644 index 0000000..8697d9b --- /dev/null +++ b/Web/src/assets/style/dark/index.less @@ -0,0 +1,35 @@ +@import './extend.less'; +@import './lib/visibility.less'; +@import './lib/container.less'; +@import './lib/align.less'; +@import './lib/font-size.less'; +@import './lib/text-color.less'; +@import './lib/margin.less'; +@import './lib/width-height.less'; +@import './lib/scrollbar.less'; +@import './main.less'; +@import './lib/button.less'; +@import './lib/card.less'; +@import './lib/table.less'; +@import './lib/list.less'; +@import './lib/form.less'; +@import './lib/form-page.less'; +@import './lib/page.less'; +@import './lib/description.less'; +@import './lib/input.less'; +@import './lib/select.less'; +@import './lib/checkbox.less'; +@import './lib/radio.less'; +@import './lib/cascader.less'; +@import './lib/upload.less'; +@import './lib/dropdown.less'; +@import './lib/modal.less'; +@import './lib/tree-layout.less'; +@import './lib/authority-view.less'; +@import './lib/icon-selector.less'; +@import './lib/color-selector.less'; +@import './lib/anchor.less'; +@import './lib/disabled.less'; +@import './theme/primary.less'; +@import './public.less'; +@import './pages/index.less'; diff --git a/Web/src/assets/style/dark/lib/align.less b/Web/src/assets/style/dark/lib/align.less new file mode 100644 index 0000000..50f0bda --- /dev/null +++ b/Web/src/assets/style/dark/lib/align.less @@ -0,0 +1,9 @@ +.text-left { + text-align: left !important; +} +.text-center { + text-align: center !important; +} +.text-right { + text-align: right !important; +} diff --git a/Web/src/assets/style/dark/lib/anchor.less b/Web/src/assets/style/dark/lib/anchor.less new file mode 100644 index 0000000..e37e90f --- /dev/null +++ b/Web/src/assets/style/dark/lib/anchor.less @@ -0,0 +1,11 @@ +@import (reference) '../extend.less'; +.ant-anchor-ink-ball { + width: 2px; + height: 28px; + + transform: translate(-50%, -10px); + + border: 0; + border-radius: 0; + background-color: @primary-color; +} diff --git a/Web/src/assets/style/dark/lib/authority-view.less b/Web/src/assets/style/dark/lib/authority-view.less new file mode 100644 index 0000000..94c2f71 --- /dev/null +++ b/Web/src/assets/style/dark/lib/authority-view.less @@ -0,0 +1,53 @@ +@import (reference) '../extend.less'; +.yo-authority-view { + &--container { + >.ant-descriptions-view { + border: 0; + } + } + .ant-descriptions-item-label { + width: 150px; + } + .ant-descriptions { + clear: both; + + margin-bottom: @padding-sm; + .ant-descriptions-view { + overflow: visible; + } + &:last-child { + margin-bottom: 0; + } + } + .ant-descriptions-item-content { + padding: @padding-sm @padding-md; + .yo-authority-view--checkbox { + display: inline-block; + + width: 150px; + margin: @padding-xxs 0; + .ant-checkbox-wrapper { + margin: 0; + } + } + } + .ant-card-grid { + width: 25%; + margin-bottom: @padding-sm; + padding: @padding-xs; + + cursor: pointer; + } + .ant-card { + margin-bottom: 0; + + background-color: transparent; + &-body { + margin: -1px 0 0 -1px; + padding: 0; + } + .ant-card-grid { + margin-bottom: 0; + } + } +} diff --git a/Web/src/assets/style/dark/lib/button.less b/Web/src/assets/style/dark/lib/button.less new file mode 100644 index 0000000..c14e462 --- /dev/null +++ b/Web/src/assets/style/dark/lib/button.less @@ -0,0 +1,4 @@ +@import (reference) '../extend.less'; +.ant-btn { + box-shadow: none; +} diff --git a/Web/src/assets/style/dark/lib/card.less b/Web/src/assets/style/dark/lib/card.less new file mode 100644 index 0000000..6fdc1e2 --- /dev/null +++ b/Web/src/assets/style/dark/lib/card.less @@ -0,0 +1,14 @@ +@import (reference) '../extend.less'; +.ant-card { + margin-bottom: @padding-md; +} +.ant-card-grid-hoverable { + &:hover { + box-shadow: 1px 0 0 0 #303030, + 0 1px 0 0 #303030, + 1px 1px 0 0 #303030, + 1px 0 0 0 #303030 inset, + 0 1px 0 0 #303030 inset, + @card-shadow; + } +} diff --git a/Web/src/assets/style/dark/lib/cascader.less b/Web/src/assets/style/dark/lib/cascader.less new file mode 100644 index 0000000..88e0aee --- /dev/null +++ b/Web/src/assets/style/dark/lib/cascader.less @@ -0,0 +1,6 @@ +@import (reference) '../extend.less'; +.ant-cascader-picker-arrow { + svg { + transform: scaleY(.75); + } +} diff --git a/Web/src/assets/style/dark/lib/checkbox.less b/Web/src/assets/style/dark/lib/checkbox.less new file mode 100644 index 0000000..be80e3e --- /dev/null +++ b/Web/src/assets/style/dark/lib/checkbox.less @@ -0,0 +1,10 @@ +@import (reference) '../extend.less'; +.ant-checkbox-wrapper { + margin-right: @padding-xs; + &:last-child { + margin-right: 0; + } + +.ant-checkbox-wrapper { + margin-left: 0; + } +} diff --git a/Web/src/assets/style/dark/lib/color-selector.less b/Web/src/assets/style/dark/lib/color-selector.less new file mode 100644 index 0000000..e7da66e --- /dev/null +++ b/Web/src/assets/style/dark/lib/color-selector.less @@ -0,0 +1,18 @@ +@import (reference) '../extend.less'; +.ant-select-dropdown { + .chrome-picker { + width: auto !important; + margin: -@padding-xxs 0; + + border-radius: 0 !important; + background: transparent !important; + box-shadow: none !important; + } +} +.color-selector--palette { + width: 32px; + height: 32px; + + border-radius: @border-radius-base; + box-shadow: inset 0 0 0 @border-width-base @border-color-base, inset 0 0 0 3px @black; +} diff --git a/Web/src/assets/style/dark/lib/container.less b/Web/src/assets/style/dark/lib/container.less new file mode 100644 index 0000000..fba352e --- /dev/null +++ b/Web/src/assets/style/dark/lib/container.less @@ -0,0 +1,43 @@ +@import (reference) '../extend.less'; +@container-width: 1400px; +.container-base { + margin: 0 auto; + padding: 0 @padding-md; +} +.container { + width: @container-width; + +.container-base(); +} +@media (max-width: 1400px) { + .container { + width: auto; + } +} +.container-md { + width: @container-width - 200px; + +.container-base(); +} +.container-sm { + width: @container-width - 400px; + +.container-base(); +} +.container-xs { + width: @container-width - 600px; + +.container-base(); +} +.container-xxs { + width: @container-width - 700px; + +.container-base(); +} +.container-fluid { + .container-base(); +} +.container-flex { + display: flex; + justify-content: space-between; +} diff --git a/Web/src/assets/style/dark/lib/description.less b/Web/src/assets/style/dark/lib/description.less new file mode 100644 index 0000000..cfe090a --- /dev/null +++ b/Web/src/assets/style/dark/lib/description.less @@ -0,0 +1,10 @@ +@import (reference) '../extend.less'; +.ant-descriptions-bordered { + .ant-descriptions-view { + >table { + border-collapse: collapse; + + background-color: @component-background; + } + } +} diff --git a/Web/src/assets/style/dark/lib/disabled.less b/Web/src/assets/style/dark/lib/disabled.less new file mode 100644 index 0000000..c975e17 --- /dev/null +++ b/Web/src/assets/style/dark/lib/disabled.less @@ -0,0 +1,59 @@ +@import (reference) '../extend.less'; +.ant-btn-primary-disabled, +.ant-btn-primary.disabled, +.ant-btn-primary[disabled], +.ant-btn-primary-disabled:hover, +.ant-btn-primary.disabled:hover, +.ant-btn-primary[disabled]:hover, +.ant-btn-primary-disabled:focus, +.ant-btn-primary.disabled:focus, +.ant-btn-primary[disabled]:focus, +.ant-btn-primary-disabled:active, +.ant-btn-primary.disabled:active, +.ant-btn-primary[disabled]:active, +.ant-btn-primary-disabled.active, +.ant-btn-primary.disabled.active, +.ant-btn-primary[disabled].active { + opacity: .5; + color: @btn-primary-color; + border-color: @btn-primary-bg; + background-color: @btn-primary-bg; + box-shadow: @btn-primary-shadow; + text-shadow: @btn-text-shadow; +} +.ant-btn-danger-disabled, +.ant-btn-danger.disabled, +.ant-btn-danger[disabled], +.ant-btn-danger-disabled:hover, +.ant-btn-danger.disabled:hover, +.ant-btn-danger[disabled]:hover, +.ant-btn-danger-disabled:focus, +.ant-btn-danger.disabled:focus, +.ant-btn-danger[disabled]:focus, +.ant-btn-danger-disabled:active, +.ant-btn-danger.disabled:active, +.ant-btn-danger[disabled]:active, +.ant-btn-danger-disabled.active, +.ant-btn-danger.disabled.active, +.ant-btn-danger[disabled].active { + opacity: .5; + color: @btn-danger-color; + border-color: @btn-danger-border; + background-color: @btn-danger-bg; + box-shadow: @btn-primary-shadow; + text-shadow: @btn-text-shadow; +} +.ant-radio-button-wrapper-disabled, +.ant-radio-button-wrapper-disabled:first-child, +.ant-radio-button-wrapper-disabled:hover { + opacity: .5; + color: @radio-button-color; + background-color: @radio-button-bg; +} +.ant-radio-button-wrapper-disabled.ant-radio-button-wrapper-checked { + opacity: .5; + color: @btn-primary-color; + border-color: @btn-primary-bg; + background-color: @btn-primary-bg; + box-shadow: @btn-primary-shadow; +} diff --git a/Web/src/assets/style/dark/lib/dropdown.less b/Web/src/assets/style/dark/lib/dropdown.less new file mode 100644 index 0000000..4accbdb --- /dev/null +++ b/Web/src/assets/style/dark/lib/dropdown.less @@ -0,0 +1,6 @@ +@import (reference) '../extend.less'; +.ant-dropdown-trigger { + .anticon-down { + transform: scaleY(.75); + } +} diff --git a/Web/src/assets/style/dark/lib/font-size.less b/Web/src/assets/style/dark/lib/font-size.less new file mode 100644 index 0000000..67f8742 --- /dev/null +++ b/Web/src/assets/style/dark/lib/font-size.less @@ -0,0 +1,25 @@ +@import (reference) '../extend.less'; +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 32px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 16px; +} +h6, +.h6 { + font-size: 14px; +} diff --git a/Web/src/assets/style/dark/lib/font-weight.less b/Web/src/assets/style/dark/lib/font-weight.less new file mode 100644 index 0000000..b24c9c7 --- /dev/null +++ b/Web/src/assets/style/dark/lib/font-weight.less @@ -0,0 +1,24 @@ +@import (reference) '../extend.less'; +body { + font-weight: 100; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 300; +} + +@btn-font-weight: 100; +.ant-card-meta-title { + font-weight: inherit; +} +.ant-table-thead { + >tr { + >th { + font-weight: 500; + } + } +} diff --git a/Web/src/assets/style/dark/lib/form-page.less b/Web/src/assets/style/dark/lib/form-page.less new file mode 100644 index 0000000..270d18a --- /dev/null +++ b/Web/src/assets/style/dark/lib/form-page.less @@ -0,0 +1,169 @@ +@import (reference) '../extend.less'; + +.yo-form-page { + position: relative; + + height: 100%; + + .yo-tab-external-mount { + display: flex; + flex-direction: column; + + height: 100%; + + >.ant-tabs { + >.ant-tabs-nav { + margin-bottom: 0; + padding: 0 @padding-md; + + background-color: @component-background; + + &.ant-tabs-card-bar { + .ant-tabs-nav-container { + height: @tabs-card-height + @padding-xs; + padding: (@tabs-card-height + @padding-xs - @btn-height-base) / 2 @padding-md; + } + + .ant-tabs-extra-content { + padding: (@tabs-card-height + @padding-xs - @btn-height-base) / 2 @padding-md; + } + + .ant-tabs-tab { + transition: none; + + .ant-btn(); + + &:hover { + border-color: @btn-default-border; + } + } + + .ant-tabs-tab { + line-height: @btn-height-base; + + margin-right: -1px; + } + + .ant-tabs-tab-active { + z-index: 2; + + color: @btn-primary-color; + border-color: @btn-primary-bg; + background-color: @btn-primary-bg; + + &:hover { + color: @btn-primary-color; + border-color: color(~`colorPalette('@{btn-primary-bg}', 5) `); + background-color: color(~`colorPalette('@{btn-primary-bg}', 5) `); + } + } + } + } + } + + >.yo-tab-external-mount-content { + position: relative; + + flex: 1; + + >.yo-tab-external-tabpane { + position: absolute; + top: 0; + left: 0; + + overflow: auto; + + width: 100%; + height: 100%; + + &.yo-tab-external-tabpane-inactive { + pointer-events: none; + + opacity: 0; + } + } + } + } + + &--bar { + position: sticky; + bottom: 0; + z-index: 200; + + &--with-tab { + position: absolute; + + display: flex; + align-items: flex-end; + + width: 100%; + height: 0; + padding-right: 7px; + + >.container-fluid { + width: 100%; + } + + ~.yo-tab-external-mount { + >.yo-tab-external-mount-content { + >.yo-tab-external-tabpane { + padding-bottom: @padding-xs * 2 + @btn-height-base + @border-width-base * 2; + } + } + } + } + } + + &--bar-inner { + display: flex; + justify-content: space-between; + + padding: @padding-xs @padding-md; + + border: @border-width-base @border-style-base @border-color-split; + background-color: fade(@component-background, 80%); + + backdrop-filter: blur(5px); + + >:first-child { + flex: 1; + } + + .ant-btn { + margin-left: @padding-sm; + } + } + + &--body { + >.ant-card-body { + padding: 0; + + >section { + padding: @padding-lg; + + >h5 { + padding-left: @padding-md; + + border-left: @padding-xs @border-style-base @primary-color; + } + } + } + } + + &-layout { + display: flex; + flex-direction: column; + + height: 100%; + + &--horizontal { + flex-direction: row; + } + } + + &--header { + padding: @padding-md 0; + + background-color: @component-background; + } +} \ No newline at end of file diff --git a/Web/src/assets/style/dark/lib/form.less b/Web/src/assets/style/dark/lib/form.less new file mode 100644 index 0000000..94afe69 --- /dev/null +++ b/Web/src/assets/style/dark/lib/form.less @@ -0,0 +1,401 @@ +@import (reference) '../extend.less'; +.yo-form { + &--fixed { + width: 660px; + margin: 0 auto; + } + .h1, + .h2, + .h3, + .h4, + .h5, + .h6 { + color: darken(@white, 40%); + } + .h3 { + font-size: 16px; + } + .h4 { + font-size: 15px; + } + .yo-form-group { + margin-bottom: @padding-md; + } + .ant-form-item { + display: flex; + justify-content: space-between; + + margin-bottom: -1px; + padding: @padding-xs @padding-md; + + border: @border-width-base @border-style-base @border-color-split; + background-color: @component-background; + + @box-shadow-focused: 0 0 0 2px fade(@primary-color, 50%); + @control-background: darken(@white, 80%) !important; + &::before, + &::after { + content: none; + } + .ant-form-item-control { + text-align: right; + } + .ant-input, + .ant-input-number, + .ant-mentions, + .ant-select-selector, + .ant-input-group-addon, + .ant-cascader-picker, + .ant-input-affix-wrapper, + .ant-picker { + z-index: 1; + + text-align: left; + + color: darken(@white, 10%); + border: 0; + background-color: @control-background; + } + .ant-mentions { + textarea { + background-color: @control-background; + } + } + .focus { + z-index: 2 !important; + + box-shadow: @box-shadow-focused; + } + .unfoucs { + z-index: 1 !important; + + box-shadow: none; + } + .ant-input { + &:focus { + .focus(); + } + } + .ant-input-affix-wrapper { + >.ant-input { + &:focus { + .unfoucs(); + } + } + } + .ant-input-number-focused, + .ant-mentions-focused { + .focus(); + } + .ant-select-focused, + .ant-select-open { + z-index: 2; + .ant-select-selection { + .focus(); + } + } + .ant-cascader-picker:focus { + .ant-cascader-input { + .focus(); + } + } + .ant-input-affix-wrapper:focus, + .ant-input-affix-wrapper-focused { + .focus(); + } + .ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) { + .ant-select-selector { + .focus(); + } + } + .ant-picker-focused { + .focus(); + } + .ant-input-group { + .ant-row-flex { + .ant-select { + width: 100%; + } + } + .ant-input-group-addon { + z-index: 0; + } + } + .ant-cascader-picker-clear { + background-color: @control-background; + } + } + .ant-form-item-label { + overflow: hidden; + flex: 1 1 auto; + + margin-right: @padding-md; + + text-align: left; + text-overflow: ellipsis; + >label { + color: darken(@white, 10%); + &::after { + content: none; + } + } + } + .ant-form-item-control { + flex: 0 0 61.8%; + + width: 61.8%; + min-width: 220px; + } + .yo-form--fluid { + .ant-form-item-control { + flex: 0 0 100%; + + width: 100%; + + text-align: inherit; + } + } + .yo-form--short { + .ant-form-item-control { + flex: 0 0 38.2%; + + width: 38.2%; + } + } + // 上下布局 + .yo-form--vertical { + display: block; + .ant-form-item-control { + text-align: left; + } + &-radio { + .ant-radio-wrapper { + line-height: @padding-lg; + + display: block; + + margin-right: 0; + +.ant-radio-wrapper { + margin-top: @padding-sm; + } + } + } + .ant-form-item-control-wrapper { + margin-left: @padding-lg; + } + .ant-form-explain { + margin-left: 0; + } + } + .yo-form-link { + display: flex; + align-items: center; + + margin-bottom: -1px; + padding: @padding-md; + + cursor: pointer; + + border: @border-width-base @border-style-base @border-color-split; + background-color: @component-background; + &:hover { + background-color: lighten(@black, 1%); + } + &:active { + background-color: lighten(@black, 3%); + } + &--title { + font-size: @font-size-base + 1px; + + flex: 1; + } + &--content { + flex: 1; + + text-align: right; + + color: fade(@black, 35%); + } + &--right-icon { + margin-left: @padding-xs; + + color: fade(@black, 50%); + } + } + &.yo-form--no-border { + .ant-form-item { + padding: @padding-md 0; + + border-right: 0; + border-left: 0; + &:first-child { + border-top: 0; + } + &:last-child { + border-bottom: 0; + } + } + .yo-form-group { + margin-bottom: 0; + } + } +} +.yo-modal-form { + .ant-modal-body { + padding: 0; + } + .yo-form { + h1, + h2, + h3, + h4, + h5 { + margin: 0; + padding: @padding-sm @padding-md @padding-xs; + } + .yo-form-group { + margin-bottom: 0; + } + .ant-form-item { + border-right: 0; + border-left: 0; + &:first-child { + margin-top: -1px; + } + } + } +} +.yo-drawer-form { + .ant-drawer-wrapper-body { + display: flex; + flex-direction: column; + } + .ant-drawer-header { + flex: 0 0 auto; + } + .ant-drawer-body { + position: relative; + + flex: 1 1 100%; + + padding: 0; + } + .yo-drawer-form--body { + position: absolute; + top: 0; + bottom: @border-width-base + 20px + @padding-md * 2; + + overflow: auto; + + width: 100%; + padding: @padding-lg; + } + .ant-drawer-footer { + position: absolute; + left: 0; + bottom: 0; + + width: 100%; + padding: 10px @padding-md; + + text-align: right; + + border-top: @border-width-base @border-style-base @border-color-split; + background: @component-background; + button+button { + margin-left: @padding-xs; + } + } +} +.ant-form { + fieldset { + margin-bottom: @padding-lg; + padding: @padding-md; + + border: @border-width-base @border-style-base @border-color-split; + } + legend { + display: inline-block; + + width: auto; + margin-bottom: 0; + padding: 0 @padding-md; + + border: 0; + border-radius: @border-radius-base; + } +} +.ant-form-horizontal { + .ant-form-item-label { + line-height: 1.5; + + margin-right: @padding-xs; + + white-space: normal; + } +} +.ant-form-vertical { + .ant-form-item-label { + >label { + font-weight: bold; + } + } +} +.ant-form-item-required { + &::before { + content: '' !important; + vertical-align: middle; + + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-left: 5px solid @highlight-color; + background: none; + } +} +.yo-form-page { + .ant-form { + .ant-radio-button-wrapper { + margin-right: @padding-xs; + margin-bottom: @padding-xs; + + border-left: @border-width-base @border-style-base @border-color-base; + &.ant-radio-button-wrapper-checked { + border-left-color: @primary-color; + } + &:not(:first-child) { + &::before { + content: none; + } + } + } + } +} +.yo-filter-item { + display: flex; + flex-flow: row wrap; + + margin-bottom: 0; + .ant-tag-checkable { + font-size: @font-size-base; + } + .ant-radio-button-wrapper { + border: 0 !important; + background-color: transparent; + &:hover { + color: @red-6; + } + } + .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { + border-color: @red-6; + background-color: @red-6; + &:hover { + border-color: @red-5; + background-color: @red-5; + } + &:active { + border-color: @red-7; + background-color: @red-7; + box-shadow: none; + } + } +} diff --git a/Web/src/assets/style/dark/lib/icon-selector.less b/Web/src/assets/style/dark/lib/icon-selector.less new file mode 100644 index 0000000..3438d23 --- /dev/null +++ b/Web/src/assets/style/dark/lib/icon-selector.less @@ -0,0 +1,59 @@ +@import (reference) '../extend.less'; +.yo-icon-selector { + .ant-drawer-wrapper-body { + display: flex; + flex-direction: column; + } + .ant-drawer-body { + position: relative; + + flex: 1 1 100%; + + padding: 0; + } + .ant-tabs { + height: 100%; + .ant-tabs-content-left { + position: relative; + + height: 100%; + .ant-tabs-tabpane { + position: absolute; + top: 0; + left: 0; + + overflow-y: auto; + + width: 100%; + height: 100%; + padding: @padding-lg; + } + } + } + .ant-card { + margin: 0; + } + .ant-card-grid { + width: 25%; + + text-align: center; + >span { + font-size: @font-size-sm; + + display: block; + + margin: @padding-xxs -@padding-lg 0; + + white-space: nowrap; + + color: fade(@black, 50%); + } + &.yo-icon--selected { + color: @white; + background-color: @primary-color; + >span { + color: fade(@white, 50%); + } + } + } +} diff --git a/Web/src/assets/style/dark/lib/input.less b/Web/src/assets/style/dark/lib/input.less new file mode 100644 index 0000000..d464a3c --- /dev/null +++ b/Web/src/assets/style/dark/lib/input.less @@ -0,0 +1,4 @@ +@import (reference) '../extend.less'; +.yo-addon { + padding: 0 @padding-xs; +} diff --git a/Web/src/assets/style/dark/lib/list.less b/Web/src/assets/style/dark/lib/list.less new file mode 100644 index 0000000..2dbb2de --- /dev/null +++ b/Web/src/assets/style/dark/lib/list.less @@ -0,0 +1,95 @@ +@import (reference) '../extend.less'; +.ant-list-bordered { + border-color: @border-color-split; + background-color: @white; +} +.yo-list { + @title-color: lighten(@black, 70%); + @value-color: lighten(@black, 30%); + &-content--h { + display: flex; + align-items: center; + &--item { + margin-left: @padding-xl; + >span { + line-height: 20px; + + color: @title-color; + } + >p { + line-height: 22px; + + margin-top: @padding-xxs; + margin-bottom: 0; + + color: @value-color; + } + } + } + .ant-pagination { + margin: @padding-md 0; + } + .ant-descriptions { + .ant-descriptions-item-label { + color: @title-color; + } + .ant-descriptions-item-content { + color: @value-color; + } + .ant-descriptions-row { + &:last-child { + >td { + padding-bottom: 0; + } + } + } + } + &--scroll { + position: relative; + + overflow-x: auto; + } + .ant-list-items { + min-width: 1000px; + } + .ant-list-item { + transition: @animation-duration-slow; + transition-property: background, border-bottom-color; + &:hover { + border-bottom-color: lighten(@primary-color, 30%); + background: linear-gradient(90deg, transparent 10%, @background-color-light 70%, transparent); + } + } + &-container { + position: relative; + &::before, + &::after { + position: absolute; + top: 0; + bottom: 0; + z-index: 3; + + width: 30px; + + content: ''; + transition: box-shadow @animation-duration-slow; + pointer-events: none; + } + &::before { + left: 0; + } + &::after { + right: 0; + } + &.yo-list--ping-left { + &::before { + box-shadow: inset 10px 0 8px -8px fade(@black, 15%); + } + } + &.yo-list--ping-right { + &::after { + box-shadow: inset -10px 0 8px -8px fade(@black, 15%); + } + } + } +} diff --git a/Web/src/assets/style/dark/lib/margin.less b/Web/src/assets/style/dark/lib/margin.less new file mode 100644 index 0000000..bdc8235 --- /dev/null +++ b/Web/src/assets/style/dark/lib/margin.less @@ -0,0 +1,68 @@ +@import (reference) '../extend.less'; +@margin-padding-position: ~'', ~'-top', ~'-left', ~'-right', ~'-bottom'; +@margin-padding-position-name: ~'', ~'t', ~'l', ~'r', ~'b'; + +.margin-padding (@i) when (@i <=length(@margin-padding-position)) { + @position: extract(@margin-padding-position, @i); + @name: extract(@margin-padding-position-name, @i); + + .m@{name}-xl { + margin@{position}: @padding-xl !important; + } + + .m@{name}-lg { + margin@{position}: @padding-lg !important; + } + + .m@{name}-md { + margin@{position}: @padding-md !important; + } + + .m@{name}-sm { + margin@{position}: @padding-sm !important; + } + + .m@{name}-xs { + margin@{position}: @padding-xs !important; + } + + .m@{name}-xxs { + margin@{position}: @padding-xxs !important; + } + + .p@{name}-xl { + padding@{position}: @padding-xl !important; + } + + .p@{name}-lg { + padding@{position}: @padding-lg !important; + } + + .p@{name}-md { + padding@{position}: @padding-md !important; + } + + .p@{name}-sm { + padding@{position}: @padding-sm !important; + } + + .p@{name}-xs { + padding@{position}: @padding-xs !important; + } + + .p@{name}-xxs { + padding@{position}: @padding-xxs !important; + } + + .m@{name}-none { + margin@{position}: 0 !important; + } + + .p@{name}-none { + padding@{position}: 0 !important; + } + + .margin-padding(@i + 1); +} + +.margin-padding(1); \ No newline at end of file diff --git a/Web/src/assets/style/dark/lib/modal.less b/Web/src/assets/style/dark/lib/modal.less new file mode 100644 index 0000000..f87defd --- /dev/null +++ b/Web/src/assets/style/dark/lib/modal.less @@ -0,0 +1,41 @@ +@import (reference) '../extend.less'; +.ant-modal-content { + background-color: fade(@primary-color, 50%); + + backdrop-filter: blur(5px); +} +.ant-modal-header { + padding: @padding-sm @padding-md; + + border-bottom: 0; + background-color: transparent; +} +.ant-modal-title { + color: fade(@white, 85%); +} +.ant-modal-body { + background-color: @component-background; +} +.ant-modal-footer { + background-color: @component-background; +} +.ant-modal-close { + top: 10px; + right: 10px; + + color: fade(@white, 75%); + background-color: @error-color; + &:hover, + &:focus { + color: @white; + } +} +.ant-modal-close-x { + line-height: 26px; + + width: 26px; + height: 26px; +} +.ant-modal-mask { + backdrop-filter: blur(3px); +} diff --git a/Web/src/assets/style/dark/lib/page.less b/Web/src/assets/style/dark/lib/page.less new file mode 100644 index 0000000..bda6aa4 --- /dev/null +++ b/Web/src/assets/style/dark/lib/page.less @@ -0,0 +1,8 @@ +@import (reference) '../extend.less'; +.yo-page { + &--header { + padding: @padding-md 0; + + background-color: @white; + } +} diff --git a/Web/src/assets/style/dark/lib/radio.less b/Web/src/assets/style/dark/lib/radio.less new file mode 100644 index 0000000..ab0b99f --- /dev/null +++ b/Web/src/assets/style/dark/lib/radio.less @@ -0,0 +1,7 @@ +@import (reference) '../extend.less'; +.ant-radio-button-wrapper-checked { + &:not(.ant-radio-button-wrapper-disabled), + &:not(.ant-radio-button-wrapper-disabled):hover { + box-shadow: none; + } +} diff --git a/Web/src/assets/style/dark/lib/scrollbar.less b/Web/src/assets/style/dark/lib/scrollbar.less new file mode 100644 index 0000000..84b7c04 --- /dev/null +++ b/Web/src/assets/style/dark/lib/scrollbar.less @@ -0,0 +1,14 @@ +@import (reference) '../extend.less'; +::-webkit-scrollbar { + width: 7px; + height: 7px; + + background-color: fade(@white, 10%); +} +::-webkit-scrollbar-thumb { + border-radius: @border-radius-base; + background-color: fade(@white, 30%); +} +::-webkit-scrollbar-thumb:active { + background-color: fade(@white, 50%); +} diff --git a/Web/src/assets/style/dark/lib/select.less b/Web/src/assets/style/dark/lib/select.less new file mode 100644 index 0000000..5f80810 --- /dev/null +++ b/Web/src/assets/style/dark/lib/select.less @@ -0,0 +1,6 @@ +@import (reference) '../extend.less'; +.ant-select-arrow { + .anticon-down { + transform: scaleY(.75); + } +} diff --git a/Web/src/assets/style/dark/lib/table.less b/Web/src/assets/style/dark/lib/table.less new file mode 100644 index 0000000..72993b8 --- /dev/null +++ b/Web/src/assets/style/dark/lib/table.less @@ -0,0 +1,255 @@ +@import (reference) '../extend.less'; +.yo-query-bar { + margin-bottom: @padding-xs; + .ant-form-inline { + .ant-form-item { + margin-bottom: @padding-xs; + } + } +} +.yo-action-bar { + display: flex; + justify-content: space-between; + + margin-bottom: @padding-md; + &--actions { + >.ant-btn, + >.ant-btn-group { + +.ant-btn, + +.ant-btn-group { + margin-left: @padding-xs; + } + } + } +} +.ant-table { + .ant-table-container { + &::before, + &::after { + z-index: 3; + } + } +} +.ant-table-thead { + th.ant-table-column-has-sorters { + &:hover { + background-color: darken(@background-color-base, 5%); + } + } +} +.ant-table-tbody { + >tr { + >td { + transition-property: background, border-bottom-color; + } + } + >tr.ant-table-row:hover { + >td { + border-bottom-color: lighten(@primary-color, 30%); + } + } +} +.ant-table-small { + >.ant-table-content { + >.ant-table-body { + margin: 0; + >table { + >.ant-table-thead { + >tr { + >th { + background-color: @table-selected-row-bg; + } + } + } + } + } + } +} +.ant-table-thead { + >tr { + >th { + font-weight: bold; + } + } +} +.ant-table-sticky-scroll { + display: none; +} +.ant-table-expanded-row>td { + border-right: @border-width-base @border-style-base @table-border-color !important; +} +.yo-table { + .ant-table { + margin: 0 !important; + } + .border-right-none { + border-right-width: 0 !important; + &:last-child { + border-right-width: 1px !important; + } + } + .ant-table-content { + .ant-table-body { + overflow-x: auto !important; + >table { + >.ant-table-thead { + >tr { + >th { + .border-right-none(); + } + } + } + >.ant-table-tbody { + >tr { + >td { + .border-right-none(); + } + } + } + } + } + .ant-table-fixed-left { + .ant-table-thead { + >tr { + >th { + border-right-width: 0 !important; + } + } + } + .ant-table-tbody { + >tr { + >td { + border-right-width: 0 !important; + } + } + } + } + .ant-table-fixed-right { + .ant-table-fixed { + border-left-width: 0 !important; + } + .ant-table-thead { + >tr { + >th { + .border-right-none(); + } + } + } + .ant-table-tbody { + >tr { + >td { + .border-right-none(); + } + } + } + } + } + .ant-table-bordered { + >.ant-table-container { + border-top: @border-width-base @border-style-base @table-border-color; + } + } + &--row-no { + width: 30px !important; + + background-color: @table-header-bg; + } +} +.yo-table-actions { + display: inline-block; + + vertical-align: middle; + &--inner { + display: flex; + align-items: center; + + height: 18px; + } +} +.yo-table--column-setting { + width: 240px; + .ant-dropdown-menu-item { + display: flex; + align-items: center; + justify-content: space-between; + } + .anticon-pushpin { + transition: @animation-duration-slow; + transform: rotate(45deg); + + color: darken(@white, 40%); + } + .yo-table--fixed { + transform: rotate(-45deg); + } +} +.yo-menu-table { + .ant-table { + .ant-table-expand-icon-col { + width: 28px; + } + .ant-table-row-expand-icon-cell { + z-index: 1; + + padding-right: 0 !important; + + border-right: none !important; + +.ant-table-cell { + padding-left: 0; + } + } + .ant-table-tbody { + >.ant-table-expanded-row>td { + padding: 0; + + border-right: none !important; + .ant-table-wrapper { + margin-bottom: -1px; + + border: none; + .ant-table { + margin: 0 !important; + } + .ant-table-container { + border: none; + .ant-table-row-expand-icon-cell { + .ant-table-row-expand-icon { + left: @padding-md; + } + +.ant-table-cell { + padding-left: @padding-md; + } + } + .ant-table-tbody { + >tr { + &:last-child { + >td { + border-bottom: @border-width-base @border-style-base @table-border-color; + } + &:hover { + >td { + border-bottom-color: lighten(@primary-color, 30%); + } + } + } + } + } + } + } + } + } + } + .ant-card { + max-width: fit-content; + margin: @padding-sm @padding-xs @padding-sm @padding-xl; + + background: none; + .ant-card-grid { + width: 300px; + height: 90px; + padding: @padding-xs @padding-sm; + + background-color: @card-background; + } + } +} diff --git a/Web/src/assets/style/dark/lib/text-color.less b/Web/src/assets/style/dark/lib/text-color.less new file mode 100644 index 0000000..8b4e052 --- /dev/null +++ b/Web/src/assets/style/dark/lib/text-color.less @@ -0,0 +1,35 @@ +@import (reference) '../extend.less'; +.text-primary { + color: @primary-color !important; +} +.text-info { + color: @info-color !important; +} +.text-success { + color: @success-color !important; +} +.text-processing { + color: @processing-color !important; +} +.text-error, +.text-danger { + color: @error-color !important; +} +.text-highlight { + color: @highlight-color !important; +} +.text-warning { + color: @warning-color !important; +} +.text-gray { + color: fade(@white, 50%) !important; +} +.text-normal { + color: fade(@white, 30%) !important; +} +.text-white { + color: @white !important; +} +.text-black { + color: @black !important; +} diff --git a/Web/src/assets/style/dark/lib/tree-layout.less b/Web/src/assets/style/dark/lib/tree-layout.less new file mode 100644 index 0000000..5490376 --- /dev/null +++ b/Web/src/assets/style/dark/lib/tree-layout.less @@ -0,0 +1,83 @@ +@import (reference) '../extend.less'; +@import (reference) './text-color.less'; +.yo-tree-layout { + position: absolute; + top: 0; + left: 0; + + width: 100%; + height: 100%; + .ant-layout-sider { + background-color: @component-background; + .ant-layout-header { + height: @layout-header-height - 20px; + + background-color: @component-background; + .header-actions { + .ant-input-search { + margin: (@layout-header-height - 20px - 32px) / 2 @padding-md; + } + } + } + } + &--collapsed { + position: absolute; + top: 0; + left: 0; + bottom: 0; + z-index: 4; + + transform: translateX(-100%); + &.open { + transform: translateX(0); + + box-shadow: 2px 0 8px fade(@black , 20%); + } + } + &--bar { + line-height: 20px; + + height: 20px; + padding: 0 @padding-md; + + text-align: right; + >.anticon { + margin-left: @padding-xs; + + cursor: pointer; + + color: fade(@white, 50%); + &:hover { + color: fade(@white, 80%); + } + } + } + &--content { + position: absolute; + top: @layout-header-height; + left: 0; + bottom: 0; + + overflow-y: auto; + + width: 100%; + &::-webkit-scrollbar { + width: 5px; + height: 5px; + + background-color: @component-background; + } + &::-webkit-scrollbar-thumb { + background-color: transparent; + } + &:hover::-webkit-scrollbar-thumb { + background-color: fade(@white, 30%); + } + &::-webkit-scrollbar-thumb:active { + background-color: fade(@white, 45%); + } + } + .ant-tree { + .text-gray(); + } +} diff --git a/Web/src/assets/style/dark/lib/upload.less b/Web/src/assets/style/dark/lib/upload.less new file mode 100644 index 0000000..a2aa434 --- /dev/null +++ b/Web/src/assets/style/dark/lib/upload.less @@ -0,0 +1,29 @@ +@import (reference) '../extend.less'; +.ant-upload-list-text { + display: flex; + flex-wrap: wrap; + .ant-upload-list-item { + height: auto; + margin-right: @padding-xs; + } + .ant-upload-list-item-info { + position: relative; + + padding: @padding-xxs @padding-xs; + + border: @border-width-base @border-style-base @border-color-split; + .anticon-paper-clip { + top: 7.5px; + } + >span { + display: flex; + } + } + .ant-upload-list-item-card-actions { + position: relative; + + margin-left: @padding-xs; + + white-space: nowrap; + } +} diff --git a/Web/src/assets/style/dark/lib/visibility.less b/Web/src/assets/style/dark/lib/visibility.less new file mode 100644 index 0000000..a3d6235 --- /dev/null +++ b/Web/src/assets/style/dark/lib/visibility.less @@ -0,0 +1,45 @@ +@import (reference) '../extend.less'; +.hide { + visibility: hidden !important; +} +.hidden { + display: none !important; +} +.block { + display: block; +} +.inline-block { + display: inline-block; +} +.inline { + display: inline; +} +.inline-flex { + display: inline-flex; +} +.flex { + display: flex; +} +.ellipsis { + display: block; + overflow: hidden; + + white-space: nowrap; + text-overflow: ellipsis; +} +.ellipsis-line(@line) { + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + + text-overflow: ellipsis; + word-break: break-all; + + -webkit-line-clamp: @line; +} +.ellipsis-2 { + .ellipsis-line(2); +} +.ellipsis-3 { + .ellipsis-line(3); +} diff --git a/Web/src/assets/style/dark/lib/width-height.less b/Web/src/assets/style/dark/lib/width-height.less new file mode 100644 index 0000000..ae39ddc --- /dev/null +++ b/Web/src/assets/style/dark/lib/width-height.less @@ -0,0 +1,47 @@ +@import (reference) '../extend.less'; + +.width-height (@i) when (@i <=20) { + + @n : @i * 50; + @px : @n * 1px; + + .w-@{n} { + width: @px !important; + } + + .w-@{n}-min { + min-width: @px !important; + } + + .w-@{n}-max { + max-width: @px !important; + } + + .h-@{n} { + height: @px !important; + } + + .h-@{n}-min { + min-height: @px !important; + } + + .h-@{n}-max { + max-height: @px !important; + } + + .w-@{n}-p { + width: @n * 1% !important; + } + + .h-@{n}-p { + height: @n * 1% !important; + } + + .width-height(@i + 1); +} + +.width-height(0); + +.flex-1 { + flex: 1; +} \ No newline at end of file diff --git a/Web/src/assets/style/dark/main.less b/Web/src/assets/style/dark/main.less new file mode 100644 index 0000000..6e9fb0b --- /dev/null +++ b/Web/src/assets/style/dark/main.less @@ -0,0 +1,704 @@ +@import (reference) './extend.less'; +@import (reference) './lib/container.less'; +@import (reference) './lib/text-color.less'; +.yo-layout--spin { + position: absolute; + top: 0; + left: 0; + + width: 100%; + height: 100%; + + background-color: @layout-header-background; + >.ant-spin-nested-loading { + height: 100%; + >div>.ant-spin { + max-height: none; + @-webkit-keyframes borderScale { + 0% { + border: 5px solid white; + } + 50% { + border: 25px solid transparent; + } + 100% { + border: 5px solid white; + } + } + @keyframes borderScale { + 0% { + border: 5px solid white; + } + 50% { + border: 25px solid transparent; + } + 100% { + border: 5px solid white; + } + } + .loader-container { + position: absolute; + top: 50%; + left: 50%; + + box-sizing: content-box; + width: 200px; + height: 200px; + margin: 0 auto; + margin-right: -50%; + + transform: translate(-50%, -50%); + -webkit-animation: borderScale 1s infinite ease-in-out; + animation: borderScale 1s infinite ease-in-out; + + color: white; + border: 5px solid transparent; + border-radius: 50%; + >p { + font-family: 'Raleway', sans-serif; + font-size: 2em; + font-weight: bold; + + position: absolute; + top: 50%; + left: 50%; + + margin-right: -50%; + + transform: translate(-50%, -50%); + } + } + } + >.ant-spin-container { + width: 100%; + height: 100%; + &.ant-spin-blur { + opacity: 0; + } + } + } +} +.ant-layout-header { + .header-actions { + display: flex; + .header-action { + display: inline-block; + + padding: 0 @padding-md; + + cursor: pointer; + transition: @animation-duration-slow; + transition-property: background-color; + .anticon { + font-size: @font-size-base + 6px; + + transition: @animation-duration-slow; + transition-property: color; + } + &:active { + box-shadow: inset 1px 1px 10px rgba(0, 0, 0, .05); + } + // 特殊工具按钮 + .theme-toggle { + position: relative; + + overflow: hidden; + + width: 20px; + height: 20px; + margin: 7px 0; + + border-radius: 50%; + &--real { + position: relative; + + width: 20px; + height: 20px; + + transition: @animation-duration-slow background-color; + + border-radius: 50%; + background-color: fade(@white, 60%); + &::before { + position: absolute; + top: 5px; + left: 5px; + + width: 10px; + height: 10px; + + content: ''; + transition: @animation-duration-slow transform; + transform: scale(0); + + border: 2px solid @layout-header-background; + border-radius: 50%; + } + } + &--imaginary { + position: absolute; + top: 6px; + right: -6px; + + width: 18px; + height: 18px; + + transition: @animation-duration-slow transform; + transform: rotate(45deg) scaleY(1); + transform-origin: top right; + + border-radius: 50%; + background-color: @layout-header-background; + } + } + &:hover { + .theme-toggle { + &--real { + background-color: @white; + &::before { + transform: scale(1); + } + } + &--imaginary { + transform: rotate(45deg) scaleY(0); + } + } + } + } + .ant-select-auto-complete { + margin: (@layout-header-height - 10px - 30px) / 2 @padding-md; + .ant-input-affix-wrapper { + border: 0; + background-color: fade(@white, 15%); + &:focus, + &-focused { + background-color: fade(@white, 30%); + } + .ant-input { + color: fade(@white, 85%); + background-color: transparent; + } + .ant-input-suffix { + .anticon { + color: fade(@white, 60%); + } + } + } + } + } + .user-container { + z-index: 10; + + width: 32px + @padding-sm * 2; + height: @layout-header-height - 24px; + margin: 2px 0; + + transition: @animation-duration-slow; + .user-container-inner { + position: relative; + + transition: @animation-duration-slow; + + border-radius: @border-radius-base; + } + .user { + &--base { + line-height: @layout-header-height - 24px; + + position: relative; + + display: flex; + overflow: hidden; + align-items: center; + + width: 100%; + height: @layout-header-height - 24px; + padding: 0 @padding-sm; + + transition: @animation-duration-slow; + } + &--avatar { + box-shadow: 0 0 0 2px @white; + } + } + } +} +.ant-layout-content { + position: relative; + + overflow-y: auto; + >.yo-tab-external-mount { + position: absolute; + top: 0; + left: 0; + bottom: 0; + + display: flex; + flex-direction: column; + + width: 100%; + >.ant-tabs { + z-index: 5; + + overflow: visible; + >.ant-tabs-nav { + margin-bottom: 0; + + border-bottom: 0; + background-color: @layout-header-background; + box-shadow: 0 2px 12px fade(@black, 8%); + &::before { + content: none; + } + .ant-tabs-nav-container { + height: 30px; + margin-bottom: 0; + } + .ant-tabs-tab { + line-height: 30px; + + height: 30px; + margin-right: 0; + padding: 0; + + transition: none; + + border: 0; + background-color: transparent; + &:hover { + color: @white; + } + .ant-tabs-tab-btn { + transition: none; + } + &.ant-tabs-tab-active { + border-color: darken(@primary-color, 10%); + background-color: @primary-color; + .ant-tabs-tab-btn { + color: @white; + } + .ant-tabs-tab-remove { + color: fade(@white, 70%); + &:hover { + color: @white; + } + } + } + .yo-layout-tab-subtitle { + line-height: 1; + + display: inline-block; + overflow: hidden; + + max-width: 150px; + + transform: translateY(1px); + white-space: nowrap; + text-overflow: ellipsis; + + opacity: .75; + } + +.ant-tabs-tab { + margin-left: 0; + &::before { + position: absolute; + left: -.5px; + + width: 1px; + height: 24px; + + content: ''; + transform: scaleX(.5); + + background: linear-gradient(transparent, fade(@black, 30%), transparent); + } + } + .ant-dropdown-trigger { + padding: 0 @padding-md * 2 0 @padding-md; + } + .ant-tabs-tab-unclosable { + .ant-dropdown-trigger { + padding: 0 @padding-lg 0 @padding-md; + } + } + .ant-tabs-tab-remove { + line-height: 28px; + + position: absolute; + top: 0; + right: 0; + + margin: 0; + + transition: none; + } + } + .ant-tabs-nav-more { + padding: 5px @padding-md; + } + } + } + >.yo-tab-external-mount-content { + position: relative; + + height: 100%; + >.yo-tab-external-tabpane { + position: absolute; + top: 0; + left: 0; + + overflow-x: hidden; + overflow-y: auto; + + width: 100%; + height: 100%; + &.yo-tab-external-tabpane-inactive { + pointer-events: none; + + opacity: 0; + } + >iframe { + display: block; + + width: 100%; + height: 100%; + + border: 0; + } + } + } + } +} +.ant-layout-sider { + .ant-menu-inline { + border-right: 0; + } +} +.yo-nav { + padding-top: @padding-lg; + padding-bottom: @padding-lg; + &--row { + padding: 1px 0; + + column-gap: @padding-md; + column-count: 3; + } + &--col { + break-inside: avoid; + } + &--sub-item { + } + &--item-group { + font-size: @font-size-base; + line-height: 1.5; + + margin-bottom: @padding-xs; + padding-top: @padding-xs * 2; + + color: fade(@black, 35%); + border: @border-width-base @border-style-base transparent; + } + &--item { + font-size: @font-size-base; + line-height: 1.5; + + position: relative; + + margin-bottom: @padding-xs; + padding: @padding-xs @padding-sm; + + cursor: pointer; + transition: @animation-duration-fast; + + border: @border-width-base @border-style-base @border-color-split; + border-radius: @border-radius-base; + background-color: @white; + &:hover { + color: @white; + border-color: @primary-color; + background-color: @primary-color; + } + } +} +.yo-layout-sider { + height: 100%; + + background-color: @layout-header-background; + .ant-layout-sider-children { + display: flex; + flex-direction: column; + } + .logo { + font-size: @font-size-lg * 1.5; + font-weight: 500; + line-height: @layout-header-height + 10px; + + z-index: 11; + + display: flex; + overflow: hidden; + align-items: center; + flex: 0 0 @layout-header-height + 10px; + + height: @layout-header-height + 10px; + padding: 0 @padding-md 0 @padding-lg; + + color: @white; + box-shadow: none; + img { + max-height: 100%; + } + span { + margin-left: @padding-sm; + + transition: @animation-duration-slow; + transition-property: opacity; + } + } + &.ant-layout-sider-collapsed { + .logo { + span { + opacity: 0; + } + } + } + .yo-sider-nav { + position: relative; + z-index: 10; + + flex: 1 1 100%; + + box-shadow: 2px 0 8px fade(@black, 25%); + &--app { + font-size: @font-size-sm; + + margin-top: @padding-sm; + padding: 0 @padding-md; + +.text-gray(); + } + .ant-menu { + background-color: @layout-header-background; + } + .ant-menu-sub.ant-menu-inline { + background-color: fade(@white, 4%); + } + } + .swiper-container { + position: absolute; + top: 0; + left: 0; + bottom: 0; + + width: 100%; + .swiper-scrollbar { + transition: @animation-duration-slow; + transition-property: opacity; + + opacity: 0; + border-radius: @border-radius-base; + } + .swiper-scrollbar-drag { + border-radius: @border-radius-base; + background-color: fade(@white, 30%); + } + &:hover { + .swiper-scrollbar { + opacity: 1; + } + } + } + .swiper-slide { + height: auto; + min-height: 100%; + >.ant-spin-nested-loading { + height: 100%; + .ant-spin-blur { + &::after { + opacity: 0; + } + } + } + } +} +.yo-layout--left-menu, +.yo-layout--right-menu { + position: absolute; + top: 0; + left: 0; + + width: 100%; + height: 100%; + .ant-layout-header { + line-height: @layout-header-height - 20px; + + z-index: 6; + + height: @layout-header-height - 20px; + padding: 0; + + background-color: @white; + >section { + display: flex; + justify-content: space-between; + } + .header-actions { + .header-action { + line-height: @layout-header-height - 16px; + + height: @layout-header-height - 20px; + + color: fade(@black, 35%); + .anticon { + color: fade(@black, 35%); + } + &:hover { + color: @icon-color-hover; + background-color: fade(@black, 5%); + .anticon { + color: @icon-color-hover; + } + } + } + } + } + >section { + >.ant-layout-sider { + .yo-layout-sider(); + } + } +} +.yo-layout--top-nav { + position: absolute; + top: 0; + left: 0; + + display: flex; + flex-direction: column; + + width: 100%; + height: 100%; + + @layout-header-height: 54px; + .ant-layout-header { + line-height: @layout-header-height; + + z-index: 11; + + flex: 0 0 @layout-header-height; + + height: @layout-header-height; + padding: 0; + + background-color: @layout-header-background; + section { + display: flex; + justify-content: space-between; + + height: 100%; + } + .header-actions { + .header-action { + color: fade(@white, 60%); + .anticon { + color: fade(@white, 60%); + } + &:hover { + color: @white; + background-color: fade(@white, 20%); + .anticon { + color: @white; + } + } + } + } + .user-container { + margin: (@layout-header-height - 40px) / 2 0; + } + .logo { + font-size: @font-size-lg * 1.5; + font-weight: 500; + line-height: @layout-header-height - 10px; + + display: flex; + overflow: hidden; + align-items: center; + + height: @layout-header-height 10px; + margin: 5px @padding-lg 5px 0; + + color: @white; + img { + max-height: 100%; + } + span { + margin-left: @padding-sm; + } + } + .ant-menu-horizontal { + line-height: @layout-header-height; + + border-bottom: 0; + >.ant-menu-submenu { + top: 0; + + border-bottom: 0; + } + } + .header-actions { + .header-action { + line-height: @layout-header-height - 16px; + + margin: 10px 0; + } + } + } + &--container { + .ant-layout-header { + .ant-menu-horizontal { + width: 400px; + } + } + .ant-layout-content { + .yo-tab-external-mount { + >.ant-tabs { + >.ant-tabs-bar { + .ant-tabs-nav-container { + width: @container-width - @padding-md * 2; + margin: 0 auto; + } + } + } + } + } + } + &--container-fluid { + .ant-layout-header { + .ant-menu-horizontal { + width: 800px; + } + @media (max-width: 1400px) { + .ant-menu-horizontal { + width: 600px; + } + } + } + } +} +.yo-user-popover { + width: 280px; + padding-top: 0; + .ant-popover-arrow { + display: none; + } + .ant-popover-inner-content { + padding: 0; + } +} +.yo-popover-infinite-scroll { + .ant-popover-inner-content { + overflow-y: auto; + + max-height: 300px; + } +} diff --git a/Web/src/assets/style/dark/pages/account-base.less b/Web/src/assets/style/dark/pages/account-base.less new file mode 100644 index 0000000..6ec0db8 --- /dev/null +++ b/Web/src/assets/style/dark/pages/account-base.less @@ -0,0 +1,51 @@ +@import (reference) '../extend.less'; +.yo-avatar-info { + position: relative; + + overflow: hidden; + + width: 128px; + margin: 0 auto; + + border-radius: 50%; + &--cover { + font-size: @font-size-lg * 2; + + position: absolute; + top: 0; + left: 0; + + display: flex; + align-items: center; + justify-content: center; + + width: 100%; + height: 100%; + + cursor: pointer; + transition: @animation-duration-slow; + + opacity: 0; + color: @white; + background-color: fade(@black, 50%); + &:hover { + opacity: 1; + } + } +} +.yo-avatar-cropper { + overflow: hidden; + + border-radius: @border-radius-base; + background-color: #ccc; +} +.yo-avatar-preview { + overflow: hidden; + + width: 200px; + height: 200px; + margin: 0 auto; + + border-radius: 50%; + background: #ccc; +} diff --git a/Web/src/assets/style/dark/pages/home.less b/Web/src/assets/style/dark/pages/home.less new file mode 100644 index 0000000..e6b1a5f --- /dev/null +++ b/Web/src/assets/style/dark/pages/home.less @@ -0,0 +1,42 @@ +@import (reference) '../extend.less'; +.home-header { + margin-bottom: @padding-md; + padding: @padding-lg 0; + + background-color: @component-background; +} +.home-header-row { + display: flex; +} +.home-header-content { + margin-left: @padding-lg; + h4 { + span { + color: @primary-color; + } + } + p { + margin: 0; + } +} +.home-container { + .ant-card-meta-title { + font-size: @font-size-base + 1px; + + display: -webkit-box; + -webkit-box-orient: vertical; + + height: 42px; + + white-space: normal; + + -webkit-line-clamp: 2; + } + .ant-card-meta-description { + .ant-row { + line-height: 24px; + + height: 24px; + } + } +} diff --git a/Web/src/assets/style/dark/pages/index.less b/Web/src/assets/style/dark/pages/index.less new file mode 100644 index 0000000..7927e7a --- /dev/null +++ b/Web/src/assets/style/dark/pages/index.less @@ -0,0 +1,3 @@ +@import './login.less'; +@import './home.less'; +@import './account-base.less'; diff --git a/Web/src/assets/style/dark/pages/login.less b/Web/src/assets/style/dark/pages/login.less new file mode 100644 index 0000000..d4a7bcd --- /dev/null +++ b/Web/src/assets/style/dark/pages/login.less @@ -0,0 +1,89 @@ +@import (reference) '../extend.less'; +.yo-login { + position: fixed; + top: 0; + left: 0; + + width: 100%; + height: 100%; + >img { + display: block; + + width: 100%; + height: 100%; + + object-fit: cover; + } + &::before { + position: absolute; + top: 0; + left: 0; + + width: 100%; + height: 100%; + + content: ''; + + background: fade(@black, 30%) url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABZJREFUeNpiMLJ0+w8EDIwgAgQAAgwAUdAHrAFSJ6cAAAAASUVORK5CYII=); + } + &--placeholder { + position: absolute; + top: 50%; + left: 0; + + width: 100%; + height: 0; + .container-sm { + display: flex; + align-items: center; + justify-content: flex-end; + + height: 0; + } + } + .ant-form { + width: 300px; + padding: @padding-lg; + + border-radius: @border-radius-base + 2px; + background: linear-gradient(45deg, @component-background, fade(@component-background, 80%)); + } + .ant-form-item { + margin-bottom: 0; + } + .ant-form-item-label { + padding: @padding-xs 0 0 !important; + + transition: @animation-duration-base; + transform: translate(0); + >label { + font-weight: normal !important; + + color: fade(@white, 40%); + } + } + &--label { + .ant-form-item-label { + transform: translate(11px, 28px); + } + } + .ant-input, + .ant-input-affix-wrapper { + color: fade(@white, 85%); + border-width: 0 0 @border-width-base 0 !important; + border-color: fade(@white, 10%); + background-color: transparent; + } + .ant-input:hover, + .ant-input:focus, + .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover, + .ant-input-affix-wrapper:focus, + .ant-input-affix-wrapper-focused { + border-width: 0 0 @border-width-base 0 !important; + border-color: @primary-color; + box-shadow: none !important; + } + .ant-input::placeholder { + font-size: @font-size-base; + } +} diff --git a/Web/src/assets/style/dark/public.less b/Web/src/assets/style/dark/public.less new file mode 100644 index 0000000..ee4716d --- /dev/null +++ b/Web/src/assets/style/dark/public.less @@ -0,0 +1,45 @@ +@import (reference) './extend.less'; +.yo-map { + &-container { + position: relative; + + padding: @padding-sm; + + border: @border-width-base @border-style-base @border-color-split; + border-radius: @border-radius-base; + background-color: @component-background; + .amap-icon { + img { + width: 25px; + } + } + } + &--search { + position: absolute; + top: @padding-md; + left: @padding-md; + z-index: 20; + + width: 25%; + min-width: 300px; + + background-color: @component-background; + box-shadow: @box-shadow-base; + } +} +.yo-adorn { + &--house-top { + height: 65px; + + background: url('~assets/image/adorn/house-top-01.png') no-repeat bottom right; + } +} +a.link-gray { + color: fade(@white, 50%); + &:hover { + color: @link-hover-color; + } + &:active { + color: @link-active-color; + } +} diff --git a/Web/src/assets/style/dark/theme/README.md b/Web/src/assets/style/dark/theme/README.md new file mode 100644 index 0000000..d6f8b27 --- /dev/null +++ b/Web/src/assets/style/dark/theme/README.md @@ -0,0 +1 @@ +/** 在此文件夹中添加控制主题颜色的less文件 **/ \ No newline at end of file diff --git a/Web/src/assets/style/dark/theme/primary.less b/Web/src/assets/style/dark/theme/primary.less new file mode 100644 index 0000000..01fb33e --- /dev/null +++ b/Web/src/assets/style/dark/theme/primary.less @@ -0,0 +1,5 @@ +@import '../index.less'; +@primary-color: #00a091; +@error-color: @red-7; +@font-size-base: 13px; +@border-radius-base: 0; diff --git a/Web/src/assets/style/default/extend.less b/Web/src/assets/style/default/extend.less new file mode 100644 index 0000000..4b038cc --- /dev/null +++ b/Web/src/assets/style/default/extend.less @@ -0,0 +1,11 @@ +@import '~antd/dist/antd.less'; +@padding-xxs: 4px; +@padding-xl: 32px; +body { + line-height: 1.42857143; +} +#root { + transition: @animation-duration-slow opacity; + + opacity: 1 !important; +} diff --git a/Web/src/assets/style/default/index.less b/Web/src/assets/style/default/index.less new file mode 100644 index 0000000..8e259cf --- /dev/null +++ b/Web/src/assets/style/default/index.less @@ -0,0 +1,36 @@ +@import './extend.less'; +@import './lib/visibility.less'; +@import './lib/container.less'; +@import './lib/align.less'; +@import './lib/font-size.less'; +@import './lib/text-color.less'; +@import './lib/margin.less'; +@import './lib/width-height.less'; +@import './lib/scrollbar.less'; +@import './main.less'; +@import './lib/button.less'; +@import './lib/card.less'; +@import './lib/table.less'; +@import './lib/list.less'; +@import './lib/form.less'; +@import './lib/form-page.less'; +@import './lib/page.less'; +@import './lib/description.less'; +@import './lib/input.less'; +@import './lib/select.less'; +@import './lib/checkbox.less'; +@import './lib/radio.less'; +@import './lib/cascader.less'; +@import './lib/upload.less'; +@import './lib/dropdown.less'; +@import './lib/modal.less'; +@import './lib/tree-layout.less'; +@import './lib/authority-view.less'; +@import './lib/icon-selector.less'; +@import './lib/color-selector.less'; +@import './lib/anchor.less'; +@import './lib/disabled.less'; +@import './lib/bs.less'; +@import './theme/primary.less'; +@import './public.less'; +@import './pages/index.less'; diff --git a/Web/src/assets/style/default/lib/align.less b/Web/src/assets/style/default/lib/align.less new file mode 100644 index 0000000..50f0bda --- /dev/null +++ b/Web/src/assets/style/default/lib/align.less @@ -0,0 +1,9 @@ +.text-left { + text-align: left !important; +} +.text-center { + text-align: center !important; +} +.text-right { + text-align: right !important; +} diff --git a/Web/src/assets/style/default/lib/anchor.less b/Web/src/assets/style/default/lib/anchor.less new file mode 100644 index 0000000..e37e90f --- /dev/null +++ b/Web/src/assets/style/default/lib/anchor.less @@ -0,0 +1,11 @@ +@import (reference) '../extend.less'; +.ant-anchor-ink-ball { + width: 2px; + height: 28px; + + transform: translate(-50%, -10px); + + border: 0; + border-radius: 0; + background-color: @primary-color; +} diff --git a/Web/src/assets/style/default/lib/authority-view.less b/Web/src/assets/style/default/lib/authority-view.less new file mode 100644 index 0000000..94c2f71 --- /dev/null +++ b/Web/src/assets/style/default/lib/authority-view.less @@ -0,0 +1,53 @@ +@import (reference) '../extend.less'; +.yo-authority-view { + &--container { + >.ant-descriptions-view { + border: 0; + } + } + .ant-descriptions-item-label { + width: 150px; + } + .ant-descriptions { + clear: both; + + margin-bottom: @padding-sm; + .ant-descriptions-view { + overflow: visible; + } + &:last-child { + margin-bottom: 0; + } + } + .ant-descriptions-item-content { + padding: @padding-sm @padding-md; + .yo-authority-view--checkbox { + display: inline-block; + + width: 150px; + margin: @padding-xxs 0; + .ant-checkbox-wrapper { + margin: 0; + } + } + } + .ant-card-grid { + width: 25%; + margin-bottom: @padding-sm; + padding: @padding-xs; + + cursor: pointer; + } + .ant-card { + margin-bottom: 0; + + background-color: transparent; + &-body { + margin: -1px 0 0 -1px; + padding: 0; + } + .ant-card-grid { + margin-bottom: 0; + } + } +} diff --git a/Web/src/assets/style/default/lib/bs.less b/Web/src/assets/style/default/lib/bs.less new file mode 100644 index 0000000..2c5418f --- /dev/null +++ b/Web/src/assets/style/default/lib/bs.less @@ -0,0 +1,81 @@ +@import (reference) '../extend.less'; +.bs-list { + &-card { + background-color: @component-background; + box-shadow: inset 0 0 0 @border-width-base @border-color-split; + } + &-title { + line-height: 32px; + + height: 32px; + padding: 0 @padding-lg 0 @padding-md; + + color: @white; + border-top-right-radius: 999px; + border-bottom-right-radius: 999px; + background: linear-gradient(90deg, @primary-color, lighten(@primary-color, 20%)); + } + &-status { + line-height: 32px; + + position: relative; + + height: 32px; + padding: 0 @padding-md 0 @padding-xs; + + color: @white; + &::before { + position: absolute; + top: 0; + left: -20px; + + content: ''; + pointer-events: none; + + border-top: 32px solid; + border-left: 20px solid transparent; + } + &.success { + background-color: @success-color; + &::before { + border-top-color: @success-color; + } + } + &.warning { + background-color: @warning-color; + &::before { + border-top-color: @warning-color; + } + } + &.error { + background-color: @error-color; + &::before { + border-top-color: @error-color; + } + } + } + &-body { + padding: @padding-md; + } + &-content { + display: flex; + align-items: center; + flex-wrap: nowrap; + + gap: 8px; + } + &-icon { + font-size: @font-size-lg + 4px; + + color: #d6e0e7; + } + &-text { + overflow: hidden; + flex: 1; + + white-space: nowrap; + text-overflow: ellipsis; + + color: fade(@black, 50%); + } +} diff --git a/Web/src/assets/style/default/lib/button.less b/Web/src/assets/style/default/lib/button.less new file mode 100644 index 0000000..c14e462 --- /dev/null +++ b/Web/src/assets/style/default/lib/button.less @@ -0,0 +1,4 @@ +@import (reference) '../extend.less'; +.ant-btn { + box-shadow: none; +} diff --git a/Web/src/assets/style/default/lib/card.less b/Web/src/assets/style/default/lib/card.less new file mode 100644 index 0000000..7ae210e --- /dev/null +++ b/Web/src/assets/style/default/lib/card.less @@ -0,0 +1,4 @@ +@import (reference) '../extend.less'; +.ant-card { + margin-bottom: @padding-md; +} diff --git a/Web/src/assets/style/default/lib/cascader.less b/Web/src/assets/style/default/lib/cascader.less new file mode 100644 index 0000000..88e0aee --- /dev/null +++ b/Web/src/assets/style/default/lib/cascader.less @@ -0,0 +1,6 @@ +@import (reference) '../extend.less'; +.ant-cascader-picker-arrow { + svg { + transform: scaleY(.75); + } +} diff --git a/Web/src/assets/style/default/lib/checkbox.less b/Web/src/assets/style/default/lib/checkbox.less new file mode 100644 index 0000000..be80e3e --- /dev/null +++ b/Web/src/assets/style/default/lib/checkbox.less @@ -0,0 +1,10 @@ +@import (reference) '../extend.less'; +.ant-checkbox-wrapper { + margin-right: @padding-xs; + &:last-child { + margin-right: 0; + } + +.ant-checkbox-wrapper { + margin-left: 0; + } +} diff --git a/Web/src/assets/style/default/lib/color-selector.less b/Web/src/assets/style/default/lib/color-selector.less new file mode 100644 index 0000000..e030c16 --- /dev/null +++ b/Web/src/assets/style/default/lib/color-selector.less @@ -0,0 +1,18 @@ +@import (reference) '../extend.less'; +.ant-select-dropdown { + .chrome-picker { + width: auto !important; + margin: -@padding-xxs 0; + + border-radius: 0 !important; + background: transparent !important; + box-shadow: none !important; + } +} +.color-selector--palette { + width: 32px; + height: 32px; + + border-radius: @border-radius-base; + box-shadow: inset 0 0 0 @border-width-base @border-color-base, inset 0 0 0 3px @white; +} diff --git a/Web/src/assets/style/default/lib/container.less b/Web/src/assets/style/default/lib/container.less new file mode 100644 index 0000000..fba352e --- /dev/null +++ b/Web/src/assets/style/default/lib/container.less @@ -0,0 +1,43 @@ +@import (reference) '../extend.less'; +@container-width: 1400px; +.container-base { + margin: 0 auto; + padding: 0 @padding-md; +} +.container { + width: @container-width; + +.container-base(); +} +@media (max-width: 1400px) { + .container { + width: auto; + } +} +.container-md { + width: @container-width - 200px; + +.container-base(); +} +.container-sm { + width: @container-width - 400px; + +.container-base(); +} +.container-xs { + width: @container-width - 600px; + +.container-base(); +} +.container-xxs { + width: @container-width - 700px; + +.container-base(); +} +.container-fluid { + .container-base(); +} +.container-flex { + display: flex; + justify-content: space-between; +} diff --git a/Web/src/assets/style/default/lib/description.less b/Web/src/assets/style/default/lib/description.less new file mode 100644 index 0000000..cfe090a --- /dev/null +++ b/Web/src/assets/style/default/lib/description.less @@ -0,0 +1,10 @@ +@import (reference) '../extend.less'; +.ant-descriptions-bordered { + .ant-descriptions-view { + >table { + border-collapse: collapse; + + background-color: @component-background; + } + } +} diff --git a/Web/src/assets/style/default/lib/disabled.less b/Web/src/assets/style/default/lib/disabled.less new file mode 100644 index 0000000..c975e17 --- /dev/null +++ b/Web/src/assets/style/default/lib/disabled.less @@ -0,0 +1,59 @@ +@import (reference) '../extend.less'; +.ant-btn-primary-disabled, +.ant-btn-primary.disabled, +.ant-btn-primary[disabled], +.ant-btn-primary-disabled:hover, +.ant-btn-primary.disabled:hover, +.ant-btn-primary[disabled]:hover, +.ant-btn-primary-disabled:focus, +.ant-btn-primary.disabled:focus, +.ant-btn-primary[disabled]:focus, +.ant-btn-primary-disabled:active, +.ant-btn-primary.disabled:active, +.ant-btn-primary[disabled]:active, +.ant-btn-primary-disabled.active, +.ant-btn-primary.disabled.active, +.ant-btn-primary[disabled].active { + opacity: .5; + color: @btn-primary-color; + border-color: @btn-primary-bg; + background-color: @btn-primary-bg; + box-shadow: @btn-primary-shadow; + text-shadow: @btn-text-shadow; +} +.ant-btn-danger-disabled, +.ant-btn-danger.disabled, +.ant-btn-danger[disabled], +.ant-btn-danger-disabled:hover, +.ant-btn-danger.disabled:hover, +.ant-btn-danger[disabled]:hover, +.ant-btn-danger-disabled:focus, +.ant-btn-danger.disabled:focus, +.ant-btn-danger[disabled]:focus, +.ant-btn-danger-disabled:active, +.ant-btn-danger.disabled:active, +.ant-btn-danger[disabled]:active, +.ant-btn-danger-disabled.active, +.ant-btn-danger.disabled.active, +.ant-btn-danger[disabled].active { + opacity: .5; + color: @btn-danger-color; + border-color: @btn-danger-border; + background-color: @btn-danger-bg; + box-shadow: @btn-primary-shadow; + text-shadow: @btn-text-shadow; +} +.ant-radio-button-wrapper-disabled, +.ant-radio-button-wrapper-disabled:first-child, +.ant-radio-button-wrapper-disabled:hover { + opacity: .5; + color: @radio-button-color; + background-color: @radio-button-bg; +} +.ant-radio-button-wrapper-disabled.ant-radio-button-wrapper-checked { + opacity: .5; + color: @btn-primary-color; + border-color: @btn-primary-bg; + background-color: @btn-primary-bg; + box-shadow: @btn-primary-shadow; +} diff --git a/Web/src/assets/style/default/lib/dropdown.less b/Web/src/assets/style/default/lib/dropdown.less new file mode 100644 index 0000000..4accbdb --- /dev/null +++ b/Web/src/assets/style/default/lib/dropdown.less @@ -0,0 +1,6 @@ +@import (reference) '../extend.less'; +.ant-dropdown-trigger { + .anticon-down { + transform: scaleY(.75); + } +} diff --git a/Web/src/assets/style/default/lib/font-size.less b/Web/src/assets/style/default/lib/font-size.less new file mode 100644 index 0000000..67f8742 --- /dev/null +++ b/Web/src/assets/style/default/lib/font-size.less @@ -0,0 +1,25 @@ +@import (reference) '../extend.less'; +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 32px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 16px; +} +h6, +.h6 { + font-size: 14px; +} diff --git a/Web/src/assets/style/default/lib/font-weight.less b/Web/src/assets/style/default/lib/font-weight.less new file mode 100644 index 0000000..b24c9c7 --- /dev/null +++ b/Web/src/assets/style/default/lib/font-weight.less @@ -0,0 +1,24 @@ +@import (reference) '../extend.less'; +body { + font-weight: 100; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 300; +} + +@btn-font-weight: 100; +.ant-card-meta-title { + font-weight: inherit; +} +.ant-table-thead { + >tr { + >th { + font-weight: 500; + } + } +} diff --git a/Web/src/assets/style/default/lib/form-page.less b/Web/src/assets/style/default/lib/form-page.less new file mode 100644 index 0000000..270d18a --- /dev/null +++ b/Web/src/assets/style/default/lib/form-page.less @@ -0,0 +1,169 @@ +@import (reference) '../extend.less'; + +.yo-form-page { + position: relative; + + height: 100%; + + .yo-tab-external-mount { + display: flex; + flex-direction: column; + + height: 100%; + + >.ant-tabs { + >.ant-tabs-nav { + margin-bottom: 0; + padding: 0 @padding-md; + + background-color: @component-background; + + &.ant-tabs-card-bar { + .ant-tabs-nav-container { + height: @tabs-card-height + @padding-xs; + padding: (@tabs-card-height + @padding-xs - @btn-height-base) / 2 @padding-md; + } + + .ant-tabs-extra-content { + padding: (@tabs-card-height + @padding-xs - @btn-height-base) / 2 @padding-md; + } + + .ant-tabs-tab { + transition: none; + + .ant-btn(); + + &:hover { + border-color: @btn-default-border; + } + } + + .ant-tabs-tab { + line-height: @btn-height-base; + + margin-right: -1px; + } + + .ant-tabs-tab-active { + z-index: 2; + + color: @btn-primary-color; + border-color: @btn-primary-bg; + background-color: @btn-primary-bg; + + &:hover { + color: @btn-primary-color; + border-color: color(~`colorPalette('@{btn-primary-bg}', 5) `); + background-color: color(~`colorPalette('@{btn-primary-bg}', 5) `); + } + } + } + } + } + + >.yo-tab-external-mount-content { + position: relative; + + flex: 1; + + >.yo-tab-external-tabpane { + position: absolute; + top: 0; + left: 0; + + overflow: auto; + + width: 100%; + height: 100%; + + &.yo-tab-external-tabpane-inactive { + pointer-events: none; + + opacity: 0; + } + } + } + } + + &--bar { + position: sticky; + bottom: 0; + z-index: 200; + + &--with-tab { + position: absolute; + + display: flex; + align-items: flex-end; + + width: 100%; + height: 0; + padding-right: 7px; + + >.container-fluid { + width: 100%; + } + + ~.yo-tab-external-mount { + >.yo-tab-external-mount-content { + >.yo-tab-external-tabpane { + padding-bottom: @padding-xs * 2 + @btn-height-base + @border-width-base * 2; + } + } + } + } + } + + &--bar-inner { + display: flex; + justify-content: space-between; + + padding: @padding-xs @padding-md; + + border: @border-width-base @border-style-base @border-color-split; + background-color: fade(@component-background, 80%); + + backdrop-filter: blur(5px); + + >:first-child { + flex: 1; + } + + .ant-btn { + margin-left: @padding-sm; + } + } + + &--body { + >.ant-card-body { + padding: 0; + + >section { + padding: @padding-lg; + + >h5 { + padding-left: @padding-md; + + border-left: @padding-xs @border-style-base @primary-color; + } + } + } + } + + &-layout { + display: flex; + flex-direction: column; + + height: 100%; + + &--horizontal { + flex-direction: row; + } + } + + &--header { + padding: @padding-md 0; + + background-color: @component-background; + } +} \ No newline at end of file diff --git a/Web/src/assets/style/default/lib/form.less b/Web/src/assets/style/default/lib/form.less new file mode 100644 index 0000000..a53f1c6 --- /dev/null +++ b/Web/src/assets/style/default/lib/form.less @@ -0,0 +1,401 @@ +@import (reference) '../extend.less'; +.yo-form { + &--fixed { + width: 660px; + margin: 0 auto; + } + .h1, + .h2, + .h3, + .h4, + .h5, + .h6 { + color: darken(@white, 40%); + } + .h3 { + font-size: 16px; + } + .h4 { + font-size: 15px; + } + .yo-form-group { + margin-bottom: @padding-md; + } + .ant-form-item { + display: flex; + justify-content: space-between; + + margin-bottom: -1px; + padding: @padding-xs @padding-md; + + border: @border-width-base @border-style-base @border-color-split; + background-color: @component-background; + + @box-shadow-focused: 0 0 0 2px fade(@primary-color, 50%); + @control-background: lighten(@black, 95%) !important; + &::before, + &::after { + content: none; + } + .ant-form-item-control { + text-align: right; + } + .ant-input, + .ant-input-number, + .ant-mentions, + .ant-select-selector, + .ant-input-group-addon, + .ant-cascader-picker, + .ant-input-affix-wrapper, + .ant-picker { + z-index: 1; + + text-align: left; + + color: lighten(@black, 10%); + border: 0; + background-color: @control-background; + } + .ant-mentions { + textarea { + background-color: @control-background; + } + } + .focus { + z-index: 2 !important; + + box-shadow: @box-shadow-focused; + } + .unfoucs { + z-index: 1 !important; + + box-shadow: none; + } + .ant-input { + &:focus { + .focus(); + } + } + .ant-input-affix-wrapper { + >.ant-input { + &:focus { + .unfoucs(); + } + } + } + .ant-input-number-focused, + .ant-mentions-focused { + .focus(); + } + .ant-select-focused, + .ant-select-open { + z-index: 2; + .ant-select-selection { + .focus(); + } + } + .ant-cascader-picker:focus { + .ant-cascader-input { + .focus(); + } + } + .ant-input-affix-wrapper:focus, + .ant-input-affix-wrapper-focused { + .focus(); + } + .ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) { + .ant-select-selector { + .focus(); + } + } + .ant-picker-focused { + .focus(); + } + .ant-input-group { + .ant-row-flex { + .ant-select { + width: 100%; + } + } + .ant-input-group-addon { + z-index: 0; + } + } + .ant-cascader-picker-clear { + background-color: @control-background; + } + } + .ant-form-item-label { + overflow: hidden; + flex: 1 1 auto; + + margin-right: @padding-md; + + text-align: left; + text-overflow: ellipsis; + >label { + color: lighten(@black, 10%); + &::after { + content: none; + } + } + } + .ant-form-item-control { + flex: 0 0 61.8%; + + width: 61.8%; + min-width: 220px; + } + .yo-form--fluid { + .ant-form-item-control { + flex: 0 0 100%; + + width: 100%; + + text-align: inherit; + } + } + .yo-form--short { + .ant-form-item-control { + flex: 0 0 38.2%; + + width: 38.2%; + } + } + // 上下布局 + .yo-form--vertical { + display: block; + .ant-form-item-control { + text-align: left; + } + &-radio { + .ant-radio-wrapper { + line-height: @padding-lg; + + display: block; + + margin-right: 0; + +.ant-radio-wrapper { + margin-top: @padding-sm; + } + } + } + .ant-form-item-control-wrapper { + margin-left: @padding-lg; + } + .ant-form-explain { + margin-left: 0; + } + } + .yo-form-link { + display: flex; + align-items: center; + + margin-bottom: -1px; + padding: @padding-md; + + cursor: pointer; + + border: @border-width-base @border-style-base @border-color-split; + background-color: @white; + &:hover { + background-color: darken(@white, 1%); + } + &:active { + background-color: darken(@white, 3%); + } + &--title { + font-size: @font-size-base + 1px; + + flex: 1; + } + &--content { + flex: 1; + + text-align: right; + + color: fade(@black, 35%); + } + &--right-icon { + margin-left: @padding-xs; + + color: fade(@black, 50%); + } + } + &.yo-form--no-border { + .ant-form-item { + padding: @padding-md 0; + + border-right: 0; + border-left: 0; + &:first-child { + border-top: 0; + } + &:last-child { + border-bottom: 0; + } + } + .yo-form-group { + margin-bottom: 0; + } + } +} +.yo-modal-form { + .ant-modal-body { + padding: 0; + } + .yo-form { + h1, + h2, + h3, + h4, + h5 { + margin: 0; + padding: @padding-sm @padding-md @padding-xs; + } + .yo-form-group { + margin-bottom: 0; + } + .ant-form-item { + border-right: 0; + border-left: 0; + &:first-child { + margin-top: -1px; + } + } + } +} +.yo-drawer-form { + .ant-drawer-wrapper-body { + display: flex; + flex-direction: column; + } + .ant-drawer-header { + flex: 0 0 auto; + } + .ant-drawer-body { + position: relative; + + flex: 1 1 100%; + + padding: 0; + } + .yo-drawer-form--body { + position: absolute; + top: 0; + bottom: @border-width-base + 20px + @padding-md * 2; + + overflow: auto; + + width: 100%; + padding: @padding-lg; + } + .ant-drawer-footer { + position: absolute; + left: 0; + bottom: 0; + + width: 100%; + padding: 10px @padding-md; + + text-align: right; + + border-top: @border-width-base @border-style-base @border-color-split; + background: @component-background; + button+button { + margin-left: @padding-xs; + } + } +} +.ant-form { + fieldset { + margin-bottom: @padding-lg; + padding: @padding-md; + + border: @border-width-base @border-style-base @border-color-split; + } + legend { + display: inline-block; + + width: auto; + margin-bottom: 0; + padding: 0 @padding-md; + + border: 0; + border-radius: @border-radius-base; + } +} +.ant-form-horizontal { + .ant-form-item-label { + line-height: 1.5; + + margin-right: @padding-xs; + + white-space: normal; + } +} +.ant-form-vertical { + .ant-form-item-label { + >label { + font-weight: bold; + } + } +} +.ant-form-item-required { + &::before { + content: '' !important; + vertical-align: middle; + + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-left: 5px solid @highlight-color; + background: none; + } +} +.yo-form-page { + .ant-form { + .ant-radio-button-wrapper { + margin-right: @padding-xs; + margin-bottom: @padding-xs; + + border-left: @border-width-base @border-style-base @border-color-base; + &.ant-radio-button-wrapper-checked { + border-left-color: @primary-color; + } + &:not(:first-child) { + &::before { + content: none; + } + } + } + } +} +.yo-filter-item { + display: flex; + flex-flow: row wrap; + + margin-bottom: 0; + .ant-tag-checkable { + font-size: @font-size-base; + } + .ant-radio-button-wrapper { + border: 0 !important; + background-color: transparent; + &:hover { + color: @red-6; + } + } + .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled) { + border-color: @red-6; + background-color: @red-6; + &:hover { + border-color: @red-5; + background-color: @red-5; + } + &:active { + border-color: @red-7; + background-color: @red-7; + box-shadow: none; + } + } +} diff --git a/Web/src/assets/style/default/lib/icon-selector.less b/Web/src/assets/style/default/lib/icon-selector.less new file mode 100644 index 0000000..3438d23 --- /dev/null +++ b/Web/src/assets/style/default/lib/icon-selector.less @@ -0,0 +1,59 @@ +@import (reference) '../extend.less'; +.yo-icon-selector { + .ant-drawer-wrapper-body { + display: flex; + flex-direction: column; + } + .ant-drawer-body { + position: relative; + + flex: 1 1 100%; + + padding: 0; + } + .ant-tabs { + height: 100%; + .ant-tabs-content-left { + position: relative; + + height: 100%; + .ant-tabs-tabpane { + position: absolute; + top: 0; + left: 0; + + overflow-y: auto; + + width: 100%; + height: 100%; + padding: @padding-lg; + } + } + } + .ant-card { + margin: 0; + } + .ant-card-grid { + width: 25%; + + text-align: center; + >span { + font-size: @font-size-sm; + + display: block; + + margin: @padding-xxs -@padding-lg 0; + + white-space: nowrap; + + color: fade(@black, 50%); + } + &.yo-icon--selected { + color: @white; + background-color: @primary-color; + >span { + color: fade(@white, 50%); + } + } + } +} diff --git a/Web/src/assets/style/default/lib/input.less b/Web/src/assets/style/default/lib/input.less new file mode 100644 index 0000000..d464a3c --- /dev/null +++ b/Web/src/assets/style/default/lib/input.less @@ -0,0 +1,4 @@ +@import (reference) '../extend.less'; +.yo-addon { + padding: 0 @padding-xs; +} diff --git a/Web/src/assets/style/default/lib/list.less b/Web/src/assets/style/default/lib/list.less new file mode 100644 index 0000000..2dbb2de --- /dev/null +++ b/Web/src/assets/style/default/lib/list.less @@ -0,0 +1,95 @@ +@import (reference) '../extend.less'; +.ant-list-bordered { + border-color: @border-color-split; + background-color: @white; +} +.yo-list { + @title-color: lighten(@black, 70%); + @value-color: lighten(@black, 30%); + &-content--h { + display: flex; + align-items: center; + &--item { + margin-left: @padding-xl; + >span { + line-height: 20px; + + color: @title-color; + } + >p { + line-height: 22px; + + margin-top: @padding-xxs; + margin-bottom: 0; + + color: @value-color; + } + } + } + .ant-pagination { + margin: @padding-md 0; + } + .ant-descriptions { + .ant-descriptions-item-label { + color: @title-color; + } + .ant-descriptions-item-content { + color: @value-color; + } + .ant-descriptions-row { + &:last-child { + >td { + padding-bottom: 0; + } + } + } + } + &--scroll { + position: relative; + + overflow-x: auto; + } + .ant-list-items { + min-width: 1000px; + } + .ant-list-item { + transition: @animation-duration-slow; + transition-property: background, border-bottom-color; + &:hover { + border-bottom-color: lighten(@primary-color, 30%); + background: linear-gradient(90deg, transparent 10%, @background-color-light 70%, transparent); + } + } + &-container { + position: relative; + &::before, + &::after { + position: absolute; + top: 0; + bottom: 0; + z-index: 3; + + width: 30px; + + content: ''; + transition: box-shadow @animation-duration-slow; + pointer-events: none; + } + &::before { + left: 0; + } + &::after { + right: 0; + } + &.yo-list--ping-left { + &::before { + box-shadow: inset 10px 0 8px -8px fade(@black, 15%); + } + } + &.yo-list--ping-right { + &::after { + box-shadow: inset -10px 0 8px -8px fade(@black, 15%); + } + } + } +} diff --git a/Web/src/assets/style/default/lib/margin.less b/Web/src/assets/style/default/lib/margin.less new file mode 100644 index 0000000..bdc8235 --- /dev/null +++ b/Web/src/assets/style/default/lib/margin.less @@ -0,0 +1,68 @@ +@import (reference) '../extend.less'; +@margin-padding-position: ~'', ~'-top', ~'-left', ~'-right', ~'-bottom'; +@margin-padding-position-name: ~'', ~'t', ~'l', ~'r', ~'b'; + +.margin-padding (@i) when (@i <=length(@margin-padding-position)) { + @position: extract(@margin-padding-position, @i); + @name: extract(@margin-padding-position-name, @i); + + .m@{name}-xl { + margin@{position}: @padding-xl !important; + } + + .m@{name}-lg { + margin@{position}: @padding-lg !important; + } + + .m@{name}-md { + margin@{position}: @padding-md !important; + } + + .m@{name}-sm { + margin@{position}: @padding-sm !important; + } + + .m@{name}-xs { + margin@{position}: @padding-xs !important; + } + + .m@{name}-xxs { + margin@{position}: @padding-xxs !important; + } + + .p@{name}-xl { + padding@{position}: @padding-xl !important; + } + + .p@{name}-lg { + padding@{position}: @padding-lg !important; + } + + .p@{name}-md { + padding@{position}: @padding-md !important; + } + + .p@{name}-sm { + padding@{position}: @padding-sm !important; + } + + .p@{name}-xs { + padding@{position}: @padding-xs !important; + } + + .p@{name}-xxs { + padding@{position}: @padding-xxs !important; + } + + .m@{name}-none { + margin@{position}: 0 !important; + } + + .p@{name}-none { + padding@{position}: 0 !important; + } + + .margin-padding(@i + 1); +} + +.margin-padding(1); \ No newline at end of file diff --git a/Web/src/assets/style/default/lib/modal.less b/Web/src/assets/style/default/lib/modal.less new file mode 100644 index 0000000..f87defd --- /dev/null +++ b/Web/src/assets/style/default/lib/modal.less @@ -0,0 +1,41 @@ +@import (reference) '../extend.less'; +.ant-modal-content { + background-color: fade(@primary-color, 50%); + + backdrop-filter: blur(5px); +} +.ant-modal-header { + padding: @padding-sm @padding-md; + + border-bottom: 0; + background-color: transparent; +} +.ant-modal-title { + color: fade(@white, 85%); +} +.ant-modal-body { + background-color: @component-background; +} +.ant-modal-footer { + background-color: @component-background; +} +.ant-modal-close { + top: 10px; + right: 10px; + + color: fade(@white, 75%); + background-color: @error-color; + &:hover, + &:focus { + color: @white; + } +} +.ant-modal-close-x { + line-height: 26px; + + width: 26px; + height: 26px; +} +.ant-modal-mask { + backdrop-filter: blur(3px); +} diff --git a/Web/src/assets/style/default/lib/page.less b/Web/src/assets/style/default/lib/page.less new file mode 100644 index 0000000..bda6aa4 --- /dev/null +++ b/Web/src/assets/style/default/lib/page.less @@ -0,0 +1,8 @@ +@import (reference) '../extend.less'; +.yo-page { + &--header { + padding: @padding-md 0; + + background-color: @white; + } +} diff --git a/Web/src/assets/style/default/lib/radio.less b/Web/src/assets/style/default/lib/radio.less new file mode 100644 index 0000000..ab0b99f --- /dev/null +++ b/Web/src/assets/style/default/lib/radio.less @@ -0,0 +1,7 @@ +@import (reference) '../extend.less'; +.ant-radio-button-wrapper-checked { + &:not(.ant-radio-button-wrapper-disabled), + &:not(.ant-radio-button-wrapper-disabled):hover { + box-shadow: none; + } +} diff --git a/Web/src/assets/style/default/lib/scrollbar.less b/Web/src/assets/style/default/lib/scrollbar.less new file mode 100644 index 0000000..bcdea38 --- /dev/null +++ b/Web/src/assets/style/default/lib/scrollbar.less @@ -0,0 +1,14 @@ +@import (reference) '../extend.less'; +::-webkit-scrollbar { + width: 7px; + height: 7px; + + background-color: fade(@black, 10%); +} +::-webkit-scrollbar-thumb { + border-radius: @border-radius-base; + background-color: fade(@black, 30%); +} +::-webkit-scrollbar-thumb:active { + background-color: fade(@black, 50%); +} diff --git a/Web/src/assets/style/default/lib/select.less b/Web/src/assets/style/default/lib/select.less new file mode 100644 index 0000000..5f80810 --- /dev/null +++ b/Web/src/assets/style/default/lib/select.less @@ -0,0 +1,6 @@ +@import (reference) '../extend.less'; +.ant-select-arrow { + .anticon-down { + transform: scaleY(.75); + } +} diff --git a/Web/src/assets/style/default/lib/table.less b/Web/src/assets/style/default/lib/table.less new file mode 100644 index 0000000..72993b8 --- /dev/null +++ b/Web/src/assets/style/default/lib/table.less @@ -0,0 +1,255 @@ +@import (reference) '../extend.less'; +.yo-query-bar { + margin-bottom: @padding-xs; + .ant-form-inline { + .ant-form-item { + margin-bottom: @padding-xs; + } + } +} +.yo-action-bar { + display: flex; + justify-content: space-between; + + margin-bottom: @padding-md; + &--actions { + >.ant-btn, + >.ant-btn-group { + +.ant-btn, + +.ant-btn-group { + margin-left: @padding-xs; + } + } + } +} +.ant-table { + .ant-table-container { + &::before, + &::after { + z-index: 3; + } + } +} +.ant-table-thead { + th.ant-table-column-has-sorters { + &:hover { + background-color: darken(@background-color-base, 5%); + } + } +} +.ant-table-tbody { + >tr { + >td { + transition-property: background, border-bottom-color; + } + } + >tr.ant-table-row:hover { + >td { + border-bottom-color: lighten(@primary-color, 30%); + } + } +} +.ant-table-small { + >.ant-table-content { + >.ant-table-body { + margin: 0; + >table { + >.ant-table-thead { + >tr { + >th { + background-color: @table-selected-row-bg; + } + } + } + } + } + } +} +.ant-table-thead { + >tr { + >th { + font-weight: bold; + } + } +} +.ant-table-sticky-scroll { + display: none; +} +.ant-table-expanded-row>td { + border-right: @border-width-base @border-style-base @table-border-color !important; +} +.yo-table { + .ant-table { + margin: 0 !important; + } + .border-right-none { + border-right-width: 0 !important; + &:last-child { + border-right-width: 1px !important; + } + } + .ant-table-content { + .ant-table-body { + overflow-x: auto !important; + >table { + >.ant-table-thead { + >tr { + >th { + .border-right-none(); + } + } + } + >.ant-table-tbody { + >tr { + >td { + .border-right-none(); + } + } + } + } + } + .ant-table-fixed-left { + .ant-table-thead { + >tr { + >th { + border-right-width: 0 !important; + } + } + } + .ant-table-tbody { + >tr { + >td { + border-right-width: 0 !important; + } + } + } + } + .ant-table-fixed-right { + .ant-table-fixed { + border-left-width: 0 !important; + } + .ant-table-thead { + >tr { + >th { + .border-right-none(); + } + } + } + .ant-table-tbody { + >tr { + >td { + .border-right-none(); + } + } + } + } + } + .ant-table-bordered { + >.ant-table-container { + border-top: @border-width-base @border-style-base @table-border-color; + } + } + &--row-no { + width: 30px !important; + + background-color: @table-header-bg; + } +} +.yo-table-actions { + display: inline-block; + + vertical-align: middle; + &--inner { + display: flex; + align-items: center; + + height: 18px; + } +} +.yo-table--column-setting { + width: 240px; + .ant-dropdown-menu-item { + display: flex; + align-items: center; + justify-content: space-between; + } + .anticon-pushpin { + transition: @animation-duration-slow; + transform: rotate(45deg); + + color: darken(@white, 40%); + } + .yo-table--fixed { + transform: rotate(-45deg); + } +} +.yo-menu-table { + .ant-table { + .ant-table-expand-icon-col { + width: 28px; + } + .ant-table-row-expand-icon-cell { + z-index: 1; + + padding-right: 0 !important; + + border-right: none !important; + +.ant-table-cell { + padding-left: 0; + } + } + .ant-table-tbody { + >.ant-table-expanded-row>td { + padding: 0; + + border-right: none !important; + .ant-table-wrapper { + margin-bottom: -1px; + + border: none; + .ant-table { + margin: 0 !important; + } + .ant-table-container { + border: none; + .ant-table-row-expand-icon-cell { + .ant-table-row-expand-icon { + left: @padding-md; + } + +.ant-table-cell { + padding-left: @padding-md; + } + } + .ant-table-tbody { + >tr { + &:last-child { + >td { + border-bottom: @border-width-base @border-style-base @table-border-color; + } + &:hover { + >td { + border-bottom-color: lighten(@primary-color, 30%); + } + } + } + } + } + } + } + } + } + } + .ant-card { + max-width: fit-content; + margin: @padding-sm @padding-xs @padding-sm @padding-xl; + + background: none; + .ant-card-grid { + width: 300px; + height: 90px; + padding: @padding-xs @padding-sm; + + background-color: @card-background; + } + } +} diff --git a/Web/src/assets/style/default/lib/text-color.less b/Web/src/assets/style/default/lib/text-color.less new file mode 100644 index 0000000..6b4dd9c --- /dev/null +++ b/Web/src/assets/style/default/lib/text-color.less @@ -0,0 +1,35 @@ +@import (reference) '../extend.less'; +.text-primary { + color: @primary-color !important; +} +.text-info { + color: @info-color !important; +} +.text-success { + color: @success-color !important; +} +.text-processing { + color: @processing-color !important; +} +.text-error, +.text-danger { + color: @error-color !important; +} +.text-highlight { + color: @highlight-color !important; +} +.text-warning { + color: @warning-color !important; +} +.text-gray { + color: fade(@black, 50%) !important; +} +.text-normal { + color: fade(@black, 30%) !important; +} +.text-white { + color: @white !important; +} +.text-black { + color: @black !important; +} diff --git a/Web/src/assets/style/default/lib/tree-layout.less b/Web/src/assets/style/default/lib/tree-layout.less new file mode 100644 index 0000000..5140cea --- /dev/null +++ b/Web/src/assets/style/default/lib/tree-layout.less @@ -0,0 +1,83 @@ +@import (reference) '../extend.less'; +@import (reference) './text-color.less'; +.yo-tree-layout { + position: absolute; + top: 0; + left: 0; + + width: 100%; + height: 100%; + .ant-layout-sider { + background-color: @component-background; + .ant-layout-header { + height: @layout-header-height - 20px; + + background-color: @component-background; + .header-actions { + .ant-input-search { + margin: (@layout-header-height - 20px - 32px) / 2 @padding-md; + } + } + } + } + &--collapsed { + position: absolute; + top: 0; + left: 0; + bottom: 0; + z-index: 4; + + transform: translateX(-100%); + &.open { + transform: translateX(0); + + box-shadow: 2px 0 8px fade(@black , 20%); + } + } + &--bar { + line-height: 20px; + + height: 20px; + padding: 0 @padding-md; + + text-align: right; + >.anticon { + margin-left: @padding-xs; + + cursor: pointer; + + color: fade(@black, 50%); + &:hover { + color: fade(@black, 80%); + } + } + } + &--content { + position: absolute; + top: @layout-header-height; + left: 0; + bottom: 0; + + overflow-y: auto; + + width: 100%; + &::-webkit-scrollbar { + width: 5px; + height: 5px; + + background-color: @component-background; + } + &::-webkit-scrollbar-thumb { + background-color: transparent; + } + &:hover::-webkit-scrollbar-thumb { + background-color: fade(@black, 30%); + } + &::-webkit-scrollbar-thumb:active { + background-color: fade(@black, 45%); + } + } + .ant-tree { + .text-gray(); + } +} diff --git a/Web/src/assets/style/default/lib/upload.less b/Web/src/assets/style/default/lib/upload.less new file mode 100644 index 0000000..a2aa434 --- /dev/null +++ b/Web/src/assets/style/default/lib/upload.less @@ -0,0 +1,29 @@ +@import (reference) '../extend.less'; +.ant-upload-list-text { + display: flex; + flex-wrap: wrap; + .ant-upload-list-item { + height: auto; + margin-right: @padding-xs; + } + .ant-upload-list-item-info { + position: relative; + + padding: @padding-xxs @padding-xs; + + border: @border-width-base @border-style-base @border-color-split; + .anticon-paper-clip { + top: 7.5px; + } + >span { + display: flex; + } + } + .ant-upload-list-item-card-actions { + position: relative; + + margin-left: @padding-xs; + + white-space: nowrap; + } +} diff --git a/Web/src/assets/style/default/lib/visibility.less b/Web/src/assets/style/default/lib/visibility.less new file mode 100644 index 0000000..a3d6235 --- /dev/null +++ b/Web/src/assets/style/default/lib/visibility.less @@ -0,0 +1,45 @@ +@import (reference) '../extend.less'; +.hide { + visibility: hidden !important; +} +.hidden { + display: none !important; +} +.block { + display: block; +} +.inline-block { + display: inline-block; +} +.inline { + display: inline; +} +.inline-flex { + display: inline-flex; +} +.flex { + display: flex; +} +.ellipsis { + display: block; + overflow: hidden; + + white-space: nowrap; + text-overflow: ellipsis; +} +.ellipsis-line(@line) { + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + + text-overflow: ellipsis; + word-break: break-all; + + -webkit-line-clamp: @line; +} +.ellipsis-2 { + .ellipsis-line(2); +} +.ellipsis-3 { + .ellipsis-line(3); +} diff --git a/Web/src/assets/style/default/lib/width-height.less b/Web/src/assets/style/default/lib/width-height.less new file mode 100644 index 0000000..ae39ddc --- /dev/null +++ b/Web/src/assets/style/default/lib/width-height.less @@ -0,0 +1,47 @@ +@import (reference) '../extend.less'; + +.width-height (@i) when (@i <=20) { + + @n : @i * 50; + @px : @n * 1px; + + .w-@{n} { + width: @px !important; + } + + .w-@{n}-min { + min-width: @px !important; + } + + .w-@{n}-max { + max-width: @px !important; + } + + .h-@{n} { + height: @px !important; + } + + .h-@{n}-min { + min-height: @px !important; + } + + .h-@{n}-max { + max-height: @px !important; + } + + .w-@{n}-p { + width: @n * 1% !important; + } + + .h-@{n}-p { + height: @n * 1% !important; + } + + .width-height(@i + 1); +} + +.width-height(0); + +.flex-1 { + flex: 1; +} \ No newline at end of file diff --git a/Web/src/assets/style/default/main.less b/Web/src/assets/style/default/main.less new file mode 100644 index 0000000..f4b6848 --- /dev/null +++ b/Web/src/assets/style/default/main.less @@ -0,0 +1,698 @@ +@import (reference) './extend.less'; +@import (reference) './lib/container.less'; +@import (reference) './lib/text-color.less'; +.yo-layout--spin { + position: absolute; + top: 0; + left: 0; + + width: 100%; + height: 100%; + + background-color: @layout-header-background; + >.ant-spin-nested-loading { + height: 100%; + >div>.ant-spin { + max-height: none; + @-webkit-keyframes borderScale { + 0% { + border: 5px solid white; + } + 50% { + border: 25px solid transparent; + } + 100% { + border: 5px solid white; + } + } + @keyframes borderScale { + 0% { + border: 5px solid white; + } + 50% { + border: 25px solid transparent; + } + 100% { + border: 5px solid white; + } + } + .loader-container { + position: absolute; + top: 50%; + left: 50%; + + box-sizing: content-box; + width: 200px; + height: 200px; + margin: 0 auto; + margin-right: -50%; + + transform: translate(-50%, -50%); + -webkit-animation: borderScale 1s infinite ease-in-out; + animation: borderScale 1s infinite ease-in-out; + + color: white; + border: 5px solid transparent; + border-radius: 50%; + >p { + font-family: 'Raleway', sans-serif; + font-size: 2em; + font-weight: bold; + + position: absolute; + top: 50%; + left: 50%; + + margin-right: -50%; + + transform: translate(-50%, -50%); + } + } + } + >.ant-spin-container { + width: 100%; + height: 100%; + &.ant-spin-blur { + opacity: 0; + } + } + } +} +.ant-layout-header { + .header-actions { + display: flex; + .header-action { + display: inline-block; + + padding: 0 @padding-md; + + cursor: pointer; + transition: @animation-duration-slow; + transition-property: background-color; + .anticon { + font-size: @font-size-base + 6px; + + transition: @animation-duration-slow; + transition-property: color; + } + &:active { + box-shadow: inset 1px 1px 10px rgba(0, 0, 0, .05); + } + // 特殊工具按钮 + .theme-toggle { + position: relative; + + overflow: hidden; + + width: 20px; + height: 20px; + margin: 7px 0; + + border-radius: 50%; + &--real { + position: relative; + + width: 100%; + height: 100%; + + transition: @animation-duration-slow background-color; + + border-radius: 50%; + background-color: fade(@white, 60%); + &::before { + position: absolute; + top: 5px; + left: 5px; + + width: 10px; + height: 10px; + + content: ''; + transition: @animation-duration-slow transform; + transform: scale(1); + + border: 2px solid @layout-header-background; + border-radius: 50%; + } + } + &--imaginary { + position: absolute; + top: 6px; + right: -6px; + + width: 18px; + height: 18px; + + transition: @animation-duration-slow transform; + transform: rotate(45deg) scaleY(0); + transform-origin: top right; + + border-radius: 50%; + background-color: #334454; + } + } + &:hover { + .theme-toggle { + &--real { + background-color: @white; + &::before { + transform: scale(0); + } + } + &--imaginary { + transform: rotate(45deg) scaleY(1); + } + } + } + } + .ant-select-auto-complete { + margin: (@layout-header-height - 10px - 30px) / 2 @padding-md; + .ant-input-affix-wrapper { + border: 0; + background-color: fade(@white, 15%); + &:focus, + &-focused { + background-color: fade(@white, 30%); + } + .ant-input { + color: fade(@white, 85%); + background-color: transparent; + } + .ant-input-suffix { + .anticon { + color: fade(@white, 60%); + } + } + } + } + } + .user-container { + z-index: 10; + + width: 32px + @padding-sm * 2; + height: @layout-header-height - 24px; + margin: 2px 0; + + transition: @animation-duration-slow; + .user-container-inner { + position: relative; + + transition: @animation-duration-slow; + + border-radius: @border-radius-base; + } + .user { + &--base { + line-height: @layout-header-height - 24px; + + position: relative; + + display: flex; + overflow: hidden; + align-items: center; + + width: 100%; + height: @layout-header-height - 24px; + padding: 0 @padding-sm; + + transition: @animation-duration-slow; + } + &--avatar { + box-shadow: 0 0 0 2px @white; + } + } + } +} +.ant-layout-content { + position: relative; + + overflow-y: auto; + >.yo-tab-external-mount { + position: absolute; + top: 0; + left: 0; + bottom: 0; + + display: flex; + flex-direction: column; + + width: 100%; + >.ant-tabs { + z-index: 5; + + overflow: visible; + >.ant-tabs-nav { + margin-bottom: 0; + + border-bottom: 0; + background-color: @card-background; + box-shadow: 0 2px 12px fade(@black, 8%); + &::before { + content: none; + } + .ant-tabs-nav-container { + height: 30px; + margin-bottom: 0; + } + .ant-tabs-tab { + line-height: 30px; + + height: 30px; + margin-right: 0; + padding: 0; + + transition: none; + + border: 0; + background-color: transparent; + &:hover { + color: @black; + } + .ant-tabs-tab-btn { + transition: none; + } + &.ant-tabs-tab-active { + border-color: darken(@primary-color, 10%); + background-color: @primary-color; + .ant-tabs-tab-btn { + color: @white; + } + .ant-tabs-tab-remove { + color: fade(@white, 70%); + &:hover { + color: @white; + } + } + } + .yo-layout-tab-subtitle { + line-height: 1; + + display: inline-block; + overflow: hidden; + + max-width: 150px; + + transform: translateY(1px); + white-space: nowrap; + text-overflow: ellipsis; + + opacity: .75; + } + +.ant-tabs-tab { + margin-left: 0; + &::before { + position: absolute; + left: -.5px; + + width: 1px; + height: 24px; + + content: ''; + transform: scaleX(.5); + + background: linear-gradient(transparent, fade(@black, 30%), transparent); + } + } + .ant-dropdown-trigger { + padding: 0 @padding-md * 2 0 @padding-md; + } + .ant-tabs-tab-unclosable { + .ant-dropdown-trigger { + padding: 0 @padding-lg 0 @padding-md; + } + } + .ant-tabs-tab-remove { + line-height: 28px; + + position: absolute; + top: 0; + right: 0; + + margin: 0; + + transition: none; + } + } + .ant-tabs-nav-more { + padding: 5px @padding-md; + } + } + } + >.yo-tab-external-mount-content { + position: relative; + + height: 100%; + >.yo-tab-external-tabpane { + position: absolute; + top: 0; + left: 0; + + overflow-x: hidden; + overflow-y: auto; + + width: 100%; + height: 100%; + &.yo-tab-external-tabpane-inactive { + pointer-events: none; + + opacity: 0; + } + >iframe { + display: block; + + width: 100%; + height: 100%; + + border: 0; + } + } + } + } +} +.ant-layout-sider { + .ant-menu-inline { + border-right: 0; + } +} +.yo-nav { + padding-top: @padding-lg; + padding-bottom: @padding-lg; + &--row { + padding: 1px 0; + + column-gap: @padding-md; + column-count: 3; + } + &--col { + break-inside: avoid; + } + &--sub-item { + } + &--item-group { + font-size: @font-size-base; + line-height: 1.5; + + margin-bottom: @padding-xs; + padding-top: @padding-xs * 2; + + color: fade(@black, 35%); + border: @border-width-base @border-style-base transparent; + } + &--item { + font-size: @font-size-base; + line-height: 1.5; + + position: relative; + + margin-bottom: @padding-xs; + padding: @padding-xs @padding-sm; + + cursor: pointer; + transition: @animation-duration-fast; + + border: @border-width-base @border-style-base @border-color-split; + border-radius: @border-radius-base; + background-color: @white; + &:hover { + color: @white; + border-color: @primary-color; + background-color: @primary-color; + } + } +} +.yo-layout-sider { + height: 100%; + + background-color: @white; + .ant-layout-sider-children { + display: flex; + flex-direction: column; + } + .logo { + font-size: @font-size-lg * 1.5; + font-weight: 500; + line-height: @layout-header-height + 10px; + + z-index: 11; + + display: flex; + overflow: hidden; + align-items: center; + flex: 0 0 @layout-header-height + 10px; + + height: @layout-header-height + 10px; + padding: 0 @padding-md 0 @padding-lg; + + color: @white; + box-shadow: none; + img { + max-height: 100%; + } + span { + margin-left: @padding-sm; + + transition: @animation-duration-slow; + transition-property: opacity; + } + } + &.ant-layout-sider-collapsed { + .logo { + span { + opacity: 0; + } + } + } + .yo-sider-nav { + position: relative; + z-index: 10; + + flex: 1 1 100%; + + box-shadow: 2px 0 8px fade(@black, 25%); + &--app { + font-size: @font-size-sm; + + margin-top: @padding-sm; + padding: 0 @padding-md; + +.text-gray(); + } + } + .swiper-container { + position: absolute; + top: 0; + left: 0; + bottom: 0; + + width: 100%; + .swiper-scrollbar { + transition: @animation-duration-slow; + transition-property: opacity; + + opacity: 0; + border-radius: @border-radius-base; + } + .swiper-scrollbar-drag { + border-radius: @border-radius-base; + background-color: fade(@black, 30%); + } + &:hover { + .swiper-scrollbar { + opacity: 1; + } + } + } + .swiper-slide { + height: auto; + min-height: 100%; + >.ant-spin-nested-loading { + height: 100%; + .ant-spin-blur { + &::after { + opacity: 0; + } + } + } + } +} +.yo-layout--left-menu, +.yo-layout--right-menu { + position: absolute; + top: 0; + left: 0; + + width: 100%; + height: 100%; + .ant-layout-header { + line-height: @layout-header-height - 20px; + + z-index: 6; + + height: @layout-header-height - 20px; + padding: 0; + + background-color: @white; + >section { + display: flex; + justify-content: space-between; + } + .header-actions { + .header-action { + line-height: @layout-header-height - 16px; + + height: @layout-header-height - 20px; + + color: fade(@black, 35%); + .anticon { + color: fade(@black, 35%); + } + &:hover { + color: @icon-color-hover; + background-color: fade(@black, 5%); + .anticon { + color: @icon-color-hover; + } + } + } + } + } + >section { + >.ant-layout-sider { + .yo-layout-sider(); + } + } +} +.yo-layout--top-nav { + position: absolute; + top: 0; + left: 0; + + display: flex; + flex-direction: column; + + width: 100%; + height: 100%; + + @layout-header-height: 54px; + .ant-layout-header { + line-height: @layout-header-height; + + z-index: 11; + + flex: 0 0 @layout-header-height; + + height: @layout-header-height; + padding: 0; + + background-color: @layout-header-background; + section { + display: flex; + justify-content: space-between; + + height: 100%; + } + .header-actions { + .header-action { + color: fade(@white, 60%); + .anticon { + color: fade(@white, 60%); + } + &:hover { + color: @white; + background-color: fade(@white, 20%); + .anticon { + color: @white; + } + } + } + } + .user-container { + margin: (@layout-header-height - 40px) / 2 0; + } + .logo { + font-size: @font-size-lg * 1.5; + font-weight: 500; + line-height: @layout-header-height - 10px; + + display: flex; + overflow: hidden; + align-items: center; + + height: @layout-header-height 10px; + margin: 5px @padding-lg 5px 0; + + color: @white; + img { + max-height: 100%; + } + span { + margin-left: @padding-sm; + } + } + .ant-menu-horizontal { + line-height: @layout-header-height; + + border-bottom: 0; + >.ant-menu-submenu { + top: 0; + + border-bottom: 0; + } + } + .header-actions { + .header-action { + line-height: @layout-header-height - 16px; + + margin: 10px 0; + } + } + } + &--container { + .ant-layout-header { + .ant-menu-horizontal { + width: 400px; + } + } + .ant-layout-content { + .yo-tab-external-mount { + >.ant-tabs { + >.ant-tabs-bar { + .ant-tabs-nav-container { + width: @container-width - @padding-md * 2; + margin: 0 auto; + } + } + } + } + } + } + &--container-fluid { + .ant-layout-header { + .ant-menu-horizontal { + width: 800px; + } + @media (max-width: 1400px) { + .ant-menu-horizontal { + width: 600px; + } + } + } + } +} +.yo-user-popover { + width: 280px; + padding-top: 0; + .ant-popover-arrow { + display: none; + } + .ant-popover-inner-content { + padding: 0; + } +} +.yo-popover-infinite-scroll { + .ant-popover-inner-content { + overflow-y: auto; + + max-height: 300px; + } +} diff --git a/Web/src/assets/style/default/pages/account-base.less b/Web/src/assets/style/default/pages/account-base.less new file mode 100644 index 0000000..6ec0db8 --- /dev/null +++ b/Web/src/assets/style/default/pages/account-base.less @@ -0,0 +1,51 @@ +@import (reference) '../extend.less'; +.yo-avatar-info { + position: relative; + + overflow: hidden; + + width: 128px; + margin: 0 auto; + + border-radius: 50%; + &--cover { + font-size: @font-size-lg * 2; + + position: absolute; + top: 0; + left: 0; + + display: flex; + align-items: center; + justify-content: center; + + width: 100%; + height: 100%; + + cursor: pointer; + transition: @animation-duration-slow; + + opacity: 0; + color: @white; + background-color: fade(@black, 50%); + &:hover { + opacity: 1; + } + } +} +.yo-avatar-cropper { + overflow: hidden; + + border-radius: @border-radius-base; + background-color: #ccc; +} +.yo-avatar-preview { + overflow: hidden; + + width: 200px; + height: 200px; + margin: 0 auto; + + border-radius: 50%; + background: #ccc; +} diff --git a/Web/src/assets/style/default/pages/home.less b/Web/src/assets/style/default/pages/home.less new file mode 100644 index 0000000..e6b1a5f --- /dev/null +++ b/Web/src/assets/style/default/pages/home.less @@ -0,0 +1,42 @@ +@import (reference) '../extend.less'; +.home-header { + margin-bottom: @padding-md; + padding: @padding-lg 0; + + background-color: @component-background; +} +.home-header-row { + display: flex; +} +.home-header-content { + margin-left: @padding-lg; + h4 { + span { + color: @primary-color; + } + } + p { + margin: 0; + } +} +.home-container { + .ant-card-meta-title { + font-size: @font-size-base + 1px; + + display: -webkit-box; + -webkit-box-orient: vertical; + + height: 42px; + + white-space: normal; + + -webkit-line-clamp: 2; + } + .ant-card-meta-description { + .ant-row { + line-height: 24px; + + height: 24px; + } + } +} diff --git a/Web/src/assets/style/default/pages/index.less b/Web/src/assets/style/default/pages/index.less new file mode 100644 index 0000000..7927e7a --- /dev/null +++ b/Web/src/assets/style/default/pages/index.less @@ -0,0 +1,3 @@ +@import './login.less'; +@import './home.less'; +@import './account-base.less'; diff --git a/Web/src/assets/style/default/pages/login.less b/Web/src/assets/style/default/pages/login.less new file mode 100644 index 0000000..636988c --- /dev/null +++ b/Web/src/assets/style/default/pages/login.less @@ -0,0 +1,89 @@ +@import (reference) '../extend.less'; +.yo-login { + position: fixed; + top: 0; + left: 0; + + width: 100%; + height: 100%; + >img { + display: block; + + width: 100%; + height: 100%; + + object-fit: cover; + } + &::before { + position: absolute; + top: 0; + left: 0; + + width: 100%; + height: 100%; + + content: ''; + + background: fade(@black, 30%) url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABZJREFUeNpiMLJ0+w8EDIwgAgQAAgwAUdAHrAFSJ6cAAAAASUVORK5CYII=); + } + &--placeholder { + position: absolute; + top: 50%; + left: 0; + + width: 100%; + height: 0; + .container-sm { + display: flex; + align-items: center; + justify-content: flex-end; + + height: 0; + } + } + .ant-form { + width: 300px; + padding: @padding-lg; + + border-radius: @border-radius-base + 2px; + background: linear-gradient(45deg, @component-background, fade(@component-background, 80%)); + } + .ant-form-item { + margin-bottom: 0; + } + .ant-form-item-label { + padding: @padding-xs 0 0 !important; + + transition: @animation-duration-base; + transform: translate(0); + >label { + font-weight: normal !important; + + color: fade(@black, 40%); + } + } + &--label { + .ant-form-item-label { + transform: translate(11px, 28px); + } + } + .ant-input, + .ant-input-affix-wrapper { + color: fade(@black, 85%); + border-width: 0 0 @border-width-base 0 !important; + border-color: fade(@black, 10%); + background-color: transparent; + } + .ant-input:hover, + .ant-input:focus, + .ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover, + .ant-input-affix-wrapper:focus, + .ant-input-affix-wrapper-focused { + border-width: 0 0 @border-width-base 0 !important; + border-color: @primary-color; + box-shadow: none !important; + } + .ant-input::placeholder { + font-size: @font-size-base; + } +} diff --git a/Web/src/assets/style/default/public.less b/Web/src/assets/style/default/public.less new file mode 100644 index 0000000..16a4aed --- /dev/null +++ b/Web/src/assets/style/default/public.less @@ -0,0 +1,45 @@ +@import (reference) './extend.less'; +.yo-map { + &-container { + position: relative; + + padding: @padding-sm; + + border: @border-width-base @border-style-base @border-color-split; + border-radius: @border-radius-base; + background-color: @component-background; + .amap-icon { + img { + width: 25px; + } + } + } + &--search { + position: absolute; + top: @padding-md; + left: @padding-md; + z-index: 20; + + width: 25%; + min-width: 300px; + + background-color: @component-background; + box-shadow: @box-shadow-base; + } +} +.yo-adorn { + &--house-top { + height: 65px; + + background: url('~assets/image/adorn/house-top-01.png') no-repeat bottom right; + } +} +a.link-gray { + color: fade(@black, 50%); + &:hover { + color: @link-hover-color; + } + &:active { + color: @link-active-color; + } +} diff --git a/Web/src/assets/style/default/theme/README.md b/Web/src/assets/style/default/theme/README.md new file mode 100644 index 0000000..d6f8b27 --- /dev/null +++ b/Web/src/assets/style/default/theme/README.md @@ -0,0 +1 @@ +/** 在此文件夹中添加控制主题颜色的less文件 **/ \ No newline at end of file diff --git a/Web/src/assets/style/default/theme/primary.less b/Web/src/assets/style/default/theme/primary.less new file mode 100644 index 0000000..5b6f5a8 --- /dev/null +++ b/Web/src/assets/style/default/theme/primary.less @@ -0,0 +1,5 @@ +@import '../index.less'; +@primary-color: #007bff; +@font-size-base: 13px; +@border-radius-base: 0; +@border-color-split: hsv(0, 0, 90%); diff --git a/Web/src/common/api/index.js b/Web/src/common/api/index.js new file mode 100644 index 0000000..e9d7915 --- /dev/null +++ b/Web/src/common/api/index.js @@ -0,0 +1,196 @@ +/** + * api + * v1.2 + */ + +import axios from 'axios' +import { token } from 'common/token' +import status from './status' +/** + * 最终直接根据url名称调用接口方法 + * 例如 + * import { api } from '@/api' + * api.getItemGroupType(parmas).then(...) + */ +import urls from './requests' +import { message as Message, notification } from 'antd' + +const STATUS = status + +axios.defaults.baseURL = process.env.NODE_ENV === 'development' ? '/api' : process.env.REACT_APP_BASE_URL + +const initInstance = (options) => { + const instance = axios + .create({ + headers: { + Authorization: 'Bearer ' + token.value + }, + ...options + }) + + instance.interceptors.response.use((res) => { + if (res.data.status === STATUS.Unauthorized) { + handlerUnauthorized() + } + return res + }, (err) => { + return Promise.reject(err) + }) + return instance +} + +const errerCodes = [STATUS.BadRequest, STATUS.InternalServerError, STATUS.Forbidden] + +const errorNotification = ({ code, message }) => { + switch (message.constructor) { + case Array: + message.forEach(p => { + setTimeout(() => { + notification.error({ + duration: 30, + message: p.field, + description: p.messages.join('/'), + }) + }) + }) + break + default: + notification.error({ + duration: 30, + message: code || '错误', + description: message, + }) + break + } +} + +const errorMessage = (message) => { + Message.error(message) +} + +const handlerUnauthorized = () => { + token.value = '' + window.location.replace('/login') +} + +const api = {} + +for (let key in urls) { + + const item = urls[key] + let url = '', + method = 'get', + options = {} + if (item.constructor === String) { + url = item + } else if (item.constructor === Array) { + url = item[0] + if (item[1]) { + method = item[1].toLowerCase() + } + if (item[2]) { + options = item[2] + } + } else if (item.constructor === Object) { + url = item.url + if (item.method) { + method = item.method.toLowerCase() + } + } + + api[`${key}Await`] = function (params = {}) { + if (method === 'post') { + return initInstance(options).post(url, params) + } else { + let _params = [], + _url = url + Object.keys(params).forEach(key => { + const value = params[key] + if (value) { + switch (value.constructor) { + case Array: + _params.push(...value.map(p => `${key}=${p}`)) + break + default: + _params.push(`${key}=${value}`) + break + } + } + }) + if (_params.length) { + _url += '?' + _params.join('&') + } + return initInstance(options).get(_url) + } + } + + api[key] = function (params = {}) { + return new Promise((reslove, reject) => { + api[`${key}Await`](params) + .then((res) => { + const { data } = res + const isFile = [ArrayBuffer, Blob].includes(data.constructor) + const result = isFile ? res : data + + // 错误的返回码,以通知的形式弹出 + if (errerCodes.indexOf(data.code) >= 0) { + errorNotification(data) + reject(result) + } + + // 非文件,返回码正确,但是结果失败,以消息的形式弹出 + else if (!isFile && !data.success) { + errorMessage(data.message) + reject(result) + } + + // 未登录 + else if (data.code === STATUS.Unauthorized) { + handlerUnauthorized() + } + + else { + reslove(result) + } + }) + .catch(({ response }) => { + const { data, status } = response + if (data.constructor === String) { + errorNotification({ + message: data, + code: status + }) + } else { + errorNotification(data) + } + if (data.code === STATUS.Unauthorized) { + handlerUnauthorized() + } + reject(data) + }) + }) + } + + api[key].url = axios.defaults.baseURL + url + api[key].key = key +} + +/** + * 并发请求,与axios.all方式相同 + * 但是使用的接口函数为this.$api.[接口名]E + */ +api.$queue = function (queue) { + return new Promise((reslove) => { + axios.all(queue).then((results) => { + const res = results.map(p => p.data) + reslove(res) + }) + }) +} + +export { + axios, + urls, + api, + STATUS +} diff --git a/Web/src/common/api/requests/business/houseSafety/houseCode.js b/Web/src/common/api/requests/business/houseSafety/houseCode.js new file mode 100644 index 0000000..67c442a --- /dev/null +++ b/Web/src/common/api/requests/business/houseSafety/houseCode.js @@ -0,0 +1,9 @@ +const urls = { + houseCodeAdd: ['/houseCode/add', 'post'], + houseCodeEdit: ['/houseCode/edit', 'post'], + houseCodePage: ['/houseCode/page', 'post'], + houseCodeNo: '/houseCode/getNextNoByCode', + houseCodeDetail: '/houseCode/detail' +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/houseSafety/houseCompany.js b/Web/src/common/api/requests/business/houseSafety/houseCompany.js new file mode 100644 index 0000000..c8dee9c --- /dev/null +++ b/Web/src/common/api/requests/business/houseSafety/houseCompany.js @@ -0,0 +1,10 @@ +const urls = { + houseCompanyPage: ['/houseCompany/page', 'post'], + houseCompanyAdd: ['/houseCompany/add', 'post'], + houseCompanyEdit: ['/houseCompany/edit', 'post'], + houseCompanyDelete: ['/houseCompany/delete', 'post'], + houseCompanyDetail: '/houseCompany/detail', + houseCompanyList: '/houseCompany/list' +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/houseSafety/houseInfo.js b/Web/src/common/api/requests/business/houseSafety/houseInfo.js new file mode 100644 index 0000000..8e51acd --- /dev/null +++ b/Web/src/common/api/requests/business/houseSafety/houseInfo.js @@ -0,0 +1,8 @@ +const urls = { + houseInfoGetByTaskId: ['/houseInfo/getByTaskId', 'get'], + houseInfoSave: ['houseInfo/save', 'post'], + houseInfoCheck: ['houseInfo/check', 'post'], + houseInfoSubmitToCheck: ['/houseInfo/submitToCheck', 'post'] +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/houseSafety/houseLog.js b/Web/src/common/api/requests/business/houseSafety/houseLog.js new file mode 100644 index 0000000..4a1702d --- /dev/null +++ b/Web/src/common/api/requests/business/houseSafety/houseLog.js @@ -0,0 +1,7 @@ +const urls = { + houseLogList: '/houseLog/list', + houseLogListByInfoId: '/houseLog/listByInfoId', + houseLogListByTaskId: '/houseLog/listByTaskId', +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/houseSafety/houseMember.js b/Web/src/common/api/requests/business/houseSafety/houseMember.js new file mode 100644 index 0000000..74654c3 --- /dev/null +++ b/Web/src/common/api/requests/business/houseSafety/houseMember.js @@ -0,0 +1,15 @@ +const urls = { + houseMemberPage: ['/houseMember/page', 'post'], + houseMemberAdd: ['/houseMember/add', 'post'], + houseMemberEdit: ['/houseMember/edit', 'post'], + houseMemberDelete: ['/houseMember/delete', 'post'], + houseMemberDetail: ['/houseMember/detail', 'detail'], + houseMemberOwnRole: ['/houseMember/ownRole', 'get'], + houseMemberOwnData: ['/houseMember/ownData', 'get'], + houseMemberGrantData: ['/houseMember/grantData', 'post'], + houseMemberChangeStatus: ['/houseMember/changeStatus', 'post'], + houseMemberDefaultRole: ['/houseMember/defaultRole', 'get'], + houseMemberDefaultRoleList: ['/houseMember/defaultRoleRange', 'get'] +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/houseSafety/houseProjectInfo.js b/Web/src/common/api/requests/business/houseSafety/houseProjectInfo.js new file mode 100644 index 0000000..2ab0b53 --- /dev/null +++ b/Web/src/common/api/requests/business/houseSafety/houseProjectInfo.js @@ -0,0 +1,12 @@ +const urls = { + getHouseProjectPage: ['/houseProjectInfo/page', 'post'], + houseProejctAdd: ['/houseProjectInfo/add', 'post'], + houseProejctEdit: ['/houseProjectInfo/edit', 'post'], + houseProejctDelete: ['/houseProjectInfo/delete', 'post'], + houseProejctDetail: ['/houseProjectInfo/detail', 'get'], + houseProjectNextSort: ['/houseProjectInfo/nextSort', 'get'], + houseProjectList: ['houseProjectInfo/list', 'get'], + houseProjectGetById: ['houseProjectInfo/getById', 'get'] +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/houseSafety/houseQuery.js b/Web/src/common/api/requests/business/houseSafety/houseQuery.js new file mode 100644 index 0000000..4749d9d --- /dev/null +++ b/Web/src/common/api/requests/business/houseSafety/houseQuery.js @@ -0,0 +1,6 @@ +const urls = { + houseQueryPage: ['/houseQuery/page', 'post'], + houseQueryDetail: ['/houseQuery/detail', 'get'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/houseSafety/houseSelector.js b/Web/src/common/api/requests/business/houseSafety/houseSelector.js new file mode 100644 index 0000000..2653a44 --- /dev/null +++ b/Web/src/common/api/requests/business/houseSafety/houseSelector.js @@ -0,0 +1,8 @@ +const urls = { + houseSelect: ['/houseSelector/select', 'post'], + houseSelectRevoke: ['/houseSelector/revoke', 'post'], + houseSelectorPage: ['/houseSelector/selectorPage', 'post'], + houseSelectedPage: ['/houseSelector/selectedPage', 'post'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/houseSafety/houseTask.js b/Web/src/common/api/requests/business/houseSafety/houseTask.js new file mode 100644 index 0000000..638b9f1 --- /dev/null +++ b/Web/src/common/api/requests/business/houseSafety/houseTask.js @@ -0,0 +1,6 @@ +const urls = { + houseTaskPage: ['/houseTask/page', 'post'], + houseTaskEdit: ['/houseTask/edit', 'post'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/houseSafety/houseZone.js b/Web/src/common/api/requests/business/houseSafety/houseZone.js new file mode 100644 index 0000000..29f4a98 --- /dev/null +++ b/Web/src/common/api/requests/business/houseSafety/houseZone.js @@ -0,0 +1,15 @@ +const urls = { + /** + * 获取机构列表 + * + */ + houseZonePage: ['/houseZone/page', 'post'], + + houseZoneList: '/houseZone/list', + houseZoneAutoIncrement: '/houseZone/autoIncrement', + houseZoneAdd: ['/houseZone/add', 'post'], + houseZoneEdit: ['/houseZone/edit', 'post'], + houseZoneGetById: ['/houseZone/getById', 'get'] +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/houseSafety/index.js b/Web/src/common/api/requests/business/houseSafety/index.js new file mode 100644 index 0000000..284c5f2 --- /dev/null +++ b/Web/src/common/api/requests/business/houseSafety/index.js @@ -0,0 +1,25 @@ +import houseProjectInfo from './houseProjectInfo' +import houseZone from './houseZone' +import houseCode from './houseCode' +import houseMember from './houseMember' +import houseSelector from './houseSelector' +import houseTask from './houseTask' +import houseInfo from './houseInfo' +import houseQuery from './houseQuery' +import houseCompany from './houseCompany' +import houseLog from './houseLog' + +const urls = { + ...houseProjectInfo, + ...houseZone, + ...houseCode, + ...houseMember, + ...houseSelector, + ...houseTask, + ...houseInfo, + ...houseQuery, + ...houseCompany, + ...houseLog +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/index.js b/Web/src/common/api/requests/business/index.js new file mode 100644 index 0000000..8a479b2 --- /dev/null +++ b/Web/src/common/api/requests/business/index.js @@ -0,0 +1,9 @@ +import houseSafety from './houseSafety' +import inspection from './inspection' + +const urls = { + ...houseSafety, + ...inspection +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/inspection/index.js b/Web/src/common/api/requests/business/inspection/index.js new file mode 100644 index 0000000..6685c2d --- /dev/null +++ b/Web/src/common/api/requests/business/inspection/index.js @@ -0,0 +1,9 @@ +import inspectionOrg from './inspectionOrg' +import inspectionOrgDir from './inspectionOrgDir' + +const urls = { + ...inspectionOrg, + ...inspectionOrgDir +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/inspection/inspectionOrg.js b/Web/src/common/api/requests/business/inspection/inspectionOrg.js new file mode 100644 index 0000000..23280f5 --- /dev/null +++ b/Web/src/common/api/requests/business/inspection/inspectionOrg.js @@ -0,0 +1,14 @@ +const urls = { + inspectionOrgReviewPage: ['/inspectionOrg/reviewPage', 'post'], + inspectionOrgReviewUpdatePage: ['/inspectionOrg/reviewUpdatePage', 'post'], + inspectionOrgNewCode: ['/inspectionOrg/newCode', 'get'], + inspectionOrgRegisterStatus: ['/inspectionOrg/registerStatus', 'get'], + inspectionOrgRegister: ['/inspectionOrg/register', 'post'], + inspectionOrgUpdate: ['/inspectionOrg/update', 'post'], + inspectionOrgDetail: ['/inspectionOrg/detail', 'get'], + inspectionOrgReviewApproved: ['/inspectionOrg/reviewApproved', 'post'], + inspectionOrgReviewNotApproved: ['/inspectionOrg/reviewNotApproved', 'post'], + inspectionOrgHistory: ['/inspectionOrg/history', 'get'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/business/inspection/inspectionOrgDir.js b/Web/src/common/api/requests/business/inspection/inspectionOrgDir.js new file mode 100644 index 0000000..6865200 --- /dev/null +++ b/Web/src/common/api/requests/business/inspection/inspectionOrgDir.js @@ -0,0 +1,11 @@ +const urls = { + inspectionOrgDirPage: ['/inspectionOrgDir/page', 'post'], + inspectionOrgDirListScore: ['/inspectionOrgDir/listScore', 'post'], + inspectionOrgDirDetail: ['/inspectionOrgDir/detail', 'get'], + inspectionOrgDirSaveScore: ['/inspectionOrgDir/saveScore', 'post'], + + inspectionOrgDirPreview: ['/inspectionOrgDir/preview', 'get'], + inspectionOrgDirPublish: ['/inspectionOrgDir/publish', 'post'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/index.js b/Web/src/common/api/requests/index.js new file mode 100644 index 0000000..67c8c1a --- /dev/null +++ b/Web/src/common/api/requests/index.js @@ -0,0 +1,22 @@ +/** + * 接口的3种配置方式 + * 1.string + * 如login: '/login'\ + * 将会默认已POST方式请求接口/login + * 2.array + * 如login: ['/login', 'post'] + * 数组[0]必填,为接口地址,[1]选填,为请求方式(不区分大小写),默认为POST + * 3.object + * 如login: { url: '/login', method: 'post' } + * [url]必填,为接口地址,[method]选填,为请求方式(不区分大小写),默认为POST + */ + +import sys from './sys' +import business from './business' + +const urls = { + ...sys, + ...business +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/appManage.js b/Web/src/common/api/requests/sys/appManage.js new file mode 100644 index 0000000..0596537 --- /dev/null +++ b/Web/src/common/api/requests/sys/appManage.js @@ -0,0 +1,34 @@ +const urls = { + /** + * 系统应用列表 + */ + getAppPage: ['/sysApp/page', 'post'], + /** + * 系统应用列表 + */ + getAppList: ['/sysApp/list', 'get'], + /** + * 新增系统应用 + */ + sysAppAdd: ['/sysApp/add', 'post'], + /** + * 编辑系统应用 + * + */ + sysAppEdit: ['/sysApp/edit', 'post'], + /** + * 删除系统应用 + */ + sysAppDelete: ['/sysApp/delete', 'post'], + /** + * 设为默认应用 + */ + sysAppSetAsDefault: ['/sysApp/setAsDefault', 'post'], + /** + * 修改应用状态 + */ + sysAppChangeStatus: ['/sysApp/changeStatus', 'post'], + sysAppDetail: ['/sysApp/detail', 'get'] +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/areaManage.js b/Web/src/common/api/requests/sys/areaManage.js new file mode 100644 index 0000000..3044653 --- /dev/null +++ b/Web/src/common/api/requests/sys/areaManage.js @@ -0,0 +1,9 @@ +const urls = { + getAreaTree: ['/sysArea/tree', 'get'], + sysAreaPage: ['/sysArea/page', 'post'], + sysAreaAdd: ['/sysArea/add', 'post'], + sysAreaEdit: ['/sysArea/edit', 'post'], + sysAreaDelete: ['/sysArea/delete', 'post'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/configManage.js b/Web/src/common/api/requests/sys/configManage.js new file mode 100644 index 0000000..2730dd8 --- /dev/null +++ b/Web/src/common/api/requests/sys/configManage.js @@ -0,0 +1,28 @@ +const urls = { + /** + * 分页查询配置列表 + */ + sysConfigPage: ['/sysConfig/page', 'post'], + /** + * 添加系统参数配置 + */ + sysConfigAdd: ['/sysConfig/add', 'post'], + /** + * 编辑系统参数配置 + */ + sysConfigEdit: ['/sysConfig/edit', 'post'], + /** + * 删除系统参数配置 + */ + sysConfigDelete: ['/sysConfig/delete', 'post'], + /** + * 获取字典类型下所有字典,举例,返回格式为:[{code:"M",value:"男"},{code:"F",value:"女"}] + */ + sysDictTypeDropDown: ['/sysDictType/dropDown', 'get'], + /** + * 获取系统的所有任务列表 + */ + sysTimersGetActionClasses: ['/sysTimers/getActionClasses', 'get'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/dictDataManage.js b/Web/src/common/api/requests/sys/dictDataManage.js new file mode 100644 index 0000000..ee27492 --- /dev/null +++ b/Web/src/common/api/requests/sys/dictDataManage.js @@ -0,0 +1,24 @@ +const urls = { + /** + * 查询系统字典值 + */ + sysDictDataPage: ['/sysDictData/page', 'post'], + /** + * 添加系统字典值 + */ + sysDictDataAdd: ['/sysDictData/add', 'post'], + /** + * 编辑系统字典值 + */ + sysDictDataEdit: ['/sysDictData/edit', 'post'], + /** + * 删除系统字典值 + */ + sysDictDataDelete: ['/sysDictData/delete', 'post'], + /** + * 批量删除系统字典值 + */ + sysDictDataDeleteBatch: ['/sysDictData/deleteBatch', 'post'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/dictManage.js b/Web/src/common/api/requests/sys/dictManage.js new file mode 100644 index 0000000..ff0ff96 --- /dev/null +++ b/Web/src/common/api/requests/sys/dictManage.js @@ -0,0 +1,32 @@ +const urls = { + /** + * 分页查询系统字典类型 + */ + sysDictTypePage: ['/sysDictType/page', 'post'], + /** + * 添加系统字典类型 + */ + sysDictTypeAdd: ['/sysDictType/add', 'post'], + /** + * 编辑系统字典类型 + */ + sysDictTypeEdit: ['/sysDictType/edit', 'post'], + /** + * 删除系统字典类型 + */ + sysDictTypeDelete: ['/sysDictType/delete', 'post'], + /** + * 获取字典类型下所有字典,举例,返回格式为:[{code:"M",value:"男"},{code:"F",value:"女"}] + */ + sysDictTypeDropDown: ['/sysDictType/dropDown', 'get'], + /** + * 获取字典类型下所有的字典,可批量获取 + */ + sysDictTypeDropDowns: ['/sysDictType/dropDowns', 'get'], + /** + * 获取所有字典树 + */ + sysDictTypeTree: ['/sysDictType/tree', 'get'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/emailManage.js b/Web/src/common/api/requests/sys/emailManage.js new file mode 100644 index 0000000..96f6b11 --- /dev/null +++ b/Web/src/common/api/requests/sys/emailManage.js @@ -0,0 +1,12 @@ +const urls = { + /** + * 发送邮件 + */ + emailSendEmail: ['/email/sendEmail', 'post'], + /** + * 发送html邮件 + */ + emailSendEmailHtml: ['/email/sendEmailHtml', 'post'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/fileManage.js b/Web/src/common/api/requests/sys/fileManage.js new file mode 100644 index 0000000..87a5503 --- /dev/null +++ b/Web/src/common/api/requests/sys/fileManage.js @@ -0,0 +1,38 @@ +const urls = { + /** + * 分页查询文件信息表 + */ + sysFileInfoPage: ['/sysFileInfo/page', 'post'], + /** + * 获取全部文件信息表 + */ + sysFileInfoList: ['/sysFileInfo/list', 'get'], + /** + * 上传文件 + */ + sysFileInfoUpload: ['/sysFileInfo/upload', 'post'], + /** + * 下载文件 + */ + sysFileInfoDownload: ['/sysFileInfo/download', 'get', { + responseType: 'blob' + }], + + /** + * 查看图片 + */ + sysFileInfoPreview: ['/sysFileInfo/preview', 'get', { + responseType: 'arraybuffer' + }], + + /** + * 查看详情文件信息表 + */ + sysFileInfoDetail: ['/sysFileInfo/detail', 'get'], + /** + * 删除文件信息表 + */ + sysFileInfoDelete: ['/sysFileInfo/delete', 'post'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/index.js b/Web/src/common/api/requests/sys/index.js new file mode 100644 index 0000000..4f94a58 --- /dev/null +++ b/Web/src/common/api/requests/sys/index.js @@ -0,0 +1,47 @@ +import appManage from './appManage' +import configManage from './configManage' +import dictDataManage from './dictDataManage' +import dictManage from './dictManage' +import emailManage from './emailManage' +import fileManage from './fileManage' +import loginManage from './loginManage' +import logManage from './logManage' +import machineManage from './machineManage' +import menuManage from './menuManage' +import noticeManage from './noticeManage' +import noticeReceiveManage from './noticeReceiveManage' +import onlineUserManage from './onlineUserManage' +import orgManage from './orgManage' +import posManage from './posManage' +import roleManage from './roleManage' +import smsManage from './smsManage' +import tenantManage from './tenantManage' +import timersManage from './timersManage' +import userManage from './userManage' +import areaManage from './areaManage' + +const urls = { + ...appManage, + ...configManage, + ...dictDataManage, + ...dictManage, + ...emailManage, + ...fileManage, + ...loginManage, + ...logManage, + ...machineManage, + ...menuManage, + ...noticeManage, + ...noticeReceiveManage, + ...onlineUserManage, + ...orgManage, + ...posManage, + ...roleManage, + ...smsManage, + ...tenantManage, + ...timersManage, + ...userManage, + ...areaManage, +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/logManage.js b/Web/src/common/api/requests/sys/logManage.js new file mode 100644 index 0000000..978b54c --- /dev/null +++ b/Web/src/common/api/requests/sys/logManage.js @@ -0,0 +1,25 @@ +const urls = { + + /** + * 查询访问日志 + */ + sysVisLogPage: ['/sysVisLog/page', 'post'], + + /** + * 查询操作日志 + */ + sysOpLogPage: ['/sysOpLog/page', 'post'], + + /** + * 清空访问日志 + */ + sysVisLogDelete: ['/sysVisLog/delete', 'post'], + + /** + * 清空登录日志 + */ + sysOpLogDelete: ['/sysOpLog/delete', 'post'], + +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/loginManage.js b/Web/src/common/api/requests/sys/loginManage.js new file mode 100644 index 0000000..216a8e4 --- /dev/null +++ b/Web/src/common/api/requests/sys/loginManage.js @@ -0,0 +1,40 @@ +const urls = { + /** + * 登录 + */ + login: ['/login', 'post'], + /** + * 登录时修改密码 + */ + loginPass: ['/loginPass', 'post'], + /** + * 登出 + */ + logout: ['/logout', 'post'], + /** + * 获取登录用户信息 + */ + getLoginUser: ['/getLoginUser', 'get'], + /** + * 获取租户开关 + */ + getTenantOpen: ['/getTenantOpen', 'get'], + /** + * 获取短信验证码 + */ + getSmsCaptcha: ['/getSmsCaptcha', 'get'], + /** + * 获取验证码开关 + */ + getCaptchaOpen: ['/getCaptchaOpen', 'get'], + /** + * 获取验证图片 以及token + */ + reqGet: ['/captcha/get', 'post'], + /** + * 滑动或者点选验证 + */ + reqCheck: ['/captcha/check', 'post'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/machineManage.js b/Web/src/common/api/requests/sys/machineManage.js new file mode 100644 index 0000000..632282a --- /dev/null +++ b/Web/src/common/api/requests/sys/machineManage.js @@ -0,0 +1,12 @@ +const urls = { + /** + * + * 系统属性监控 + * + */ + sysMachineUse: ['/sysMachine/use', 'get'], + sysMachineBase: ['/sysMachine/base', 'get'], + sysMachineNetwork: ['/sysMachine/network', 'get'] +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/menuManage.js b/Web/src/common/api/requests/sys/menuManage.js new file mode 100644 index 0000000..d923007 --- /dev/null +++ b/Web/src/common/api/requests/sys/menuManage.js @@ -0,0 +1,68 @@ +const urls = { + /** + * 获取菜单列表 + * + * @author yubaoshan + * @param parameter + * @returns {*} + */ + getMenuList: ['/sysMenu/list', 'get'], + + /** + * 获取系统菜单树,用于新增,编辑时选择上级节点 + * + * @author yubaoshan + * @date 2020/4/23 12:22 + */ + getMenuTree: ['/sysMenu/tree', 'get'], + + /** + * 增加菜单 + * + * @author yubaoshan + * @date 2020/4/24 23:23 + */ + sysMenuAdd: ['/sysMenu/add', 'post'], + + /** + * 增加菜单 + * + * @author yubaoshan + * @date 2020/4/24 23:23 + */ + sysMenuDelete: ['/sysMenu/delete', 'post'], + + /** + * 查看菜单详情 + * + * @author yubaoshan + * @date 2020/4/25 01:11 + */ + sysMenuDetail: ['/sysMenu/detail', 'post'], + + /** + * 编辑系统菜单 + * + * @author yubaoshan + * @date 2020/4/25 01:11 + */ + sysMenuEdit: ['/sysMenu/edit', 'post'], + + /** + * 获取系统菜单树,用于给角色授权时选择 + * + * @author yubaoshan + * @date 2020/6/2 17:30 + */ + SysMenuTreeForGrant: ['/sysMenu/treeForGrant', 'get'], + /** + * 根据系统切换菜单 + * + * @author yubaoshan + * @date 2020/6/28 15:25 + */ + sysMenuChange: ['/sysMenu/change', 'post'], + +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/noticeManage.js b/Web/src/common/api/requests/sys/noticeManage.js new file mode 100644 index 0000000..c1c692f --- /dev/null +++ b/Web/src/common/api/requests/sys/noticeManage.js @@ -0,0 +1,33 @@ +const urls = { + /** + * 查询系统通知公告 + */ + sysNoticePage: ['/sysNotice/page', 'post'], + + /** + * 添加系统通知公告 + */ + sysNoticeAdd: ['/sysNotice/add', 'post'], + + /** + * 编辑系统通知公告 + */ + sysNoticeEdit: ['/sysNotice/edit', 'post'], + + /** + * 删除系统通知公告 + */ + sysNoticeDelete: ['/sysNotice/delete', 'post'], + + /** + * 通知公告详情 + */ + sysNoticeDetail: ['/sysNotice/detail', 'get'], + + /** + * 修改状态 + */ + sysNoticeChangeStatus: ['/sysNotice/changeStatus', 'post'], +} + +export default urls diff --git a/Web/src/common/api/requests/sys/noticeReceiveManage.js b/Web/src/common/api/requests/sys/noticeReceiveManage.js new file mode 100644 index 0000000..85d50ef --- /dev/null +++ b/Web/src/common/api/requests/sys/noticeReceiveManage.js @@ -0,0 +1,12 @@ +const urls = { + /** + * 获取接收到的通知公告总数 + */ + sysNoticeUnread: ['/sysNotice/unread', 'get'], + /** + * 查询我收到的系统通知公告 + */ + sysNoticeReceived: ['/sysNotice/received', 'post'], +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/onlineUserManage.js b/Web/src/common/api/requests/sys/onlineUserManage.js new file mode 100644 index 0000000..e87053f --- /dev/null +++ b/Web/src/common/api/requests/sys/onlineUserManage.js @@ -0,0 +1,14 @@ +const urls = { + /** + * 在线用户列表 + */ + sysOnlineUserList: ['/sysOnlineUser/list', 'get'], + + /** + * 强制下线 + */ + sysOnlineUserForceExist: ['/sysOnlineUser/forceExist', 'post'], + +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/orgManage.js b/Web/src/common/api/requests/sys/orgManage.js new file mode 100644 index 0000000..a253956 --- /dev/null +++ b/Web/src/common/api/requests/sys/orgManage.js @@ -0,0 +1,40 @@ +const urls = { + /** + * 获取机构树 + * + */ + getOrgTree: ['/sysOrg/tree', 'get'], + + /** + * 获取机构列表 + * + */ + getOrgList: ['/sysOrg/list', 'get'], + + /** + * 获取机构列表 + * + */ + getOrgPage: ['/sysOrg/page', 'post'], + + /** + * 新增机构 + * + */ + sysOrgAdd: ['/sysOrg/add', 'post'], + + /** + * 编辑机构 + * + */ + sysOrgEdit: ['/sysOrg/edit', 'post'], + + /** + * 删除机构 + * + */ + sysOrgDelete: ['/sysOrg/delete', 'post'], + +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/posManage.js b/Web/src/common/api/requests/sys/posManage.js new file mode 100644 index 0000000..c222df7 --- /dev/null +++ b/Web/src/common/api/requests/sys/posManage.js @@ -0,0 +1,34 @@ +const urls = { + /** + * 查询系统职位 + * + */ + sysPosPage: ['/sysPos/page', 'post'], + + /** + * 系统职位列表 + * + */ + sysPosList: ['/sysPos/list', 'get'], + + /** + * 添加系统职位 + * + */ + sysPosAdd: ['/sysPos/add', 'post'], + + /** + * 编辑系统职位 + * + */ + sysPosEdit: ['/sysPos/edit', 'post'], + + /** + * 删除系统职位 + * + */ + sysPosDelete: ['/sysPos/delete', 'post'], + +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/roleManage.js b/Web/src/common/api/requests/sys/roleManage.js new file mode 100644 index 0000000..ae4f795 --- /dev/null +++ b/Web/src/common/api/requests/sys/roleManage.js @@ -0,0 +1,64 @@ +const urls = { + /** + * 获取角色列表 + */ + getRolePage: ['/sysRole/page', 'post'], + + + /** + * 增加角色 + */ + sysRoleAdd: ['/sysRole/add', 'post'], + + + /** + * 编辑角色 + */ + sysRoleEdit: ['/sysRole/edit', 'post'], + + + /** + * 删除角色 + */ + sysRoleDelete: ['/sysRole/delete', 'post'], + + + /** + * 删除角色 + */ + sysRoleDeteil: ['/sysRole/detail', 'get'], + + + /** + * 获取授权角色列表 + */ + sysRoleDropDown: ['/sysRole/dropDown', 'get'], + + + /** + * 拥有菜单 + */ + sysRoleOwnMenu: ['/sysRole/ownMenu', 'get'], + + + /** + * 授权菜单 + */ + sysRoleGrantMenu: ['/sysRole/grantMenu', 'post'], + + + /** + * 拥有数据 + */ + sysRoleOwnData: ['/sysRole/ownData', 'get'], + + + /** + * 授权数据 + */ + sysRoleGrantData: ['/sysRole/grantData', 'post'], + + +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/smsManage.js b/Web/src/common/api/requests/sys/smsManage.js new file mode 100644 index 0000000..b7a3462 --- /dev/null +++ b/Web/src/common/api/requests/sys/smsManage.js @@ -0,0 +1,19 @@ +const urls = { + /** + * 发送记录查询 + */ + smsPage: ['/sms/page', 'get'], + + /** + * 验证短信验证码 + */ + sysSendLoginMessage: ['/sms/sendLoginMessage', 'post'], + + /** + * 验证短信验证码 + */ + sysValidateMessage: ['/sms/validateMessage', 'post'], + +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/tenantManage.js b/Web/src/common/api/requests/sys/tenantManage.js new file mode 100644 index 0000000..5ac3f16 --- /dev/null +++ b/Web/src/common/api/requests/sys/tenantManage.js @@ -0,0 +1,28 @@ +const urls = { + /** + * 租户列表 + * + */ + sysTenantPage: ['/sysTenant/page', 'post'], + + /** + * 新增租户 + * + */ + sysTenantAdd: ['/sysTenant/add', 'post'], + + /** + * 删除租户 + * + */ + sysTenantDelete: ['/sysTenant/delete', 'post'], + + /** + * 编辑租户 + * + */ + sysTenantEdit: ['/sysTenant/edit', 'post'], + +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/timersManage.js b/Web/src/common/api/requests/sys/timersManage.js new file mode 100644 index 0000000..2e64678 --- /dev/null +++ b/Web/src/common/api/requests/sys/timersManage.js @@ -0,0 +1,58 @@ +const urls = { + /** + * 分页查询定时任务 + */ + sysTimersPage: ['/sysTimers/page', 'post'], + + + /** + * 获取全部定时任务 + */ + sysTimersList: ['/sysTimers/list', 'get'], + + + /** + * 查看详情定时任务 + */ + sysTimersDetail: ['/sysTimers/detail', 'get'], + + + /** + * 添加定时任务 + */ + sysTimersAdd: ['/sysTimers/add', 'post'], + + + /** + * 删除定时任务 + */ + sysTimersDelete: ['/sysTimers/delete', 'post'], + + + /** + * 编辑定时任务 + */ + sysTimersEdit: ['/sysTimers/edit', 'post'], + + + /** + * 获取系统的所有任务列表 + */ + sysTimersGetActionClasses: ['/sysTimers/getActionClasses', 'post'], + + + /** + * 启动定时任务 + */ + sysTimersStart: ['/sysTimers/start', 'post'], + + + /** + * 停止定时任务 + */ + sysTimersStop: ['/sysTimers/stop', 'post'], + + +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/requests/sys/userManage.js b/Web/src/common/api/requests/sys/userManage.js new file mode 100644 index 0000000..8307459 --- /dev/null +++ b/Web/src/common/api/requests/sys/userManage.js @@ -0,0 +1,95 @@ +const urls = { + /** + * 获取用户列表 + */ + getUserPage: ['/sysUser/page', 'post'], + + /** + * 增加用户 + */ + sysUserAdd: ['/sysUser/add', 'post'], + + /** + * 编辑用户 + */ + sysUserEdit: ['/sysUser/edit', 'post'], + + /** + * 获取用户详情 + */ + sysUserDetail: ['/sysUser/detail', 'get'], + + /** + * 删除用户 + */ + sysUserDelete: ['/sysUser/delete', 'post'], + + /** + * 拥有角色 + */ + sysUserOwnRole: ['/sysUser/ownRole', 'get'], + + /** + * 授权角色 + */ + sysUserGrantRole: ['/sysUser/grantRole', 'post'], + + /** + * 拥有数据 + */ + sysUserOwnData: ['/sysUser/ownData', 'get'], + + /** + * 授权数据 + */ + sysUserGrantData: ['/sysUser/grantData', 'post'], + + /** + * 修改状态 + */ + sysUserChangeStatus: ['/sysUser/changeStatus', 'post'], + + /** + * 重置密码 + */ + sysUserResetPwd: ['/sysUser/resetPwd', 'post'], + + /** + * 修改密码 + */ + sysUserUpdatePwd: ['/sysUser/updatePwd', 'post'], + + /** + * 用户选择器 + */ + sysUserSelector: ['/sysUser/selector', 'get'], + + /** + * 修改头像 + */ + sysUserUpdateAvatar: ['/sysUser/updateAvatar', 'post'], + + /** + * 更新基本信息 + */ + sysUserUpdateInfo: ['/sysUser/updateInfo', 'post'], + + + /** + * 发送验证码 + */ + sysUserSendCode: ['/sysUser/sendCode', 'post'], + + /** + * 绑定/验证 + */ + sysUserCheckBindcode: ['/sysUser/checkBindcode', 'post'], + + /** + * 获取密码验证配置 + */ + getPwdRule: ['/sysUser/getPwdRule', 'post'], + +} + +export default urls \ No newline at end of file diff --git a/Web/src/common/api/status.js b/Web/src/common/api/status.js new file mode 100644 index 0000000..9c7c915 --- /dev/null +++ b/Web/src/common/api/status.js @@ -0,0 +1,208 @@ +const status = { + // + // 摘要: + // 等效于 HTTP 状态 100。 System.Net.HttpStatusCode.Continue 指示客户端可以继续其请求。 + Continue: 100, + // + // 摘要: + // 等效于 HTTP 状态为 101。 System.Net.HttpStatusCode.SwitchingProtocols 指示正在更改的协议版本或协议。 + SwitchingProtocols: 101, + // + // 摘要: + // 等效于 HTTP 状态 200。 System.Net.HttpStatusCode.OK 指示请求成功,且请求的信息包含在响应中。 这是要接收的最常见状态代码。 + OK: 200, + // + // 摘要: + // 等效于 HTTP 状态 201。 System.Net.HttpStatusCode.Created 指示请求导致已发送响应之前创建一个新的资源。 + Created: 201, + // + // 摘要: + // 等效于 HTTP 状态 202。 System.Net.HttpStatusCode.Accepted 指示请求已被接受进行进一步处理。 + Accepted: 202, + // + // 摘要: + // 等效于 HTTP 状态 203。 System.Net.HttpStatusCode.NonAuthoritativeInformation 指示返回的元信息来自而不是原始服务器的缓存副本,因此可能不正确。 + NonAuthoritativeInformation: 203, + // + // 摘要: + // 等效于 HTTP 状态 204。 System.Net.HttpStatusCode.NoContent 指示已成功处理请求和响应是有意留为空白。 + NoContent: 204, + // + // 摘要: + // 等效于 HTTP 状态 205。 System.Net.HttpStatusCode.ResetContent 指示客户端应重置 (而不是重新加载) 的当前资源。 + ResetContent: 205, + // + // 摘要: + // 等效于 HTTP 206 状态。 System.Net.HttpStatusCode.PartialContent 指示根据包括字节范围的 GET 请求的请求的响应是部分响应。 + PartialContent: 206, + // + // 摘要: + // 等效于 HTTP 状态 300。 System.Net.HttpStatusCode.MultipleChoices 指示所需的信息有多种表示形式。 默认操作是将此状态视为一个重定向,并按照与此响应关联的位置标头的内容。 + MultipleChoices: 300, + // + // 摘要: + // 等效于 HTTP 状态 300。 System.Net.HttpStatusCode.Ambiguous 指示所需的信息有多种表示形式。 默认操作是将此状态视为一个重定向,并按照与此响应关联的位置标头的内容。 + Ambiguous: 300, + // + // 摘要: + // 等效于 HTTP 状态 301。 System.Net.HttpStatusCode.MovedPermanently 指示已将所需的信息移动到的位置标头中指定的 + // URI。 当收到此状态时的默认操作是遵循与响应关联的位置标头。 + MovedPermanently: 301, + // + // 摘要: + // 等效于 HTTP 状态 301。 System.Net.HttpStatusCode.Moved 指示已将所需的信息移动到的位置标头中指定的 URI。 当收到此状态时的默认操作是遵循与响应关联的位置标头。 + // 当原始请求方法是 POST 时,重定向的请求将使用 GET 方法。 + Moved: 301, + // + // 摘要: + // 等效于 HTTP 状态 302。 System.Net.HttpStatusCode.Found 指示所需的信息位于的位置标头中指定的 URI。 当收到此状态时的默认操作是遵循与响应关联的位置标头。 + // 当原始请求方法是 POST 时,重定向的请求将使用 GET 方法。 + Found: 302, + // + // 摘要: + // 等效于 HTTP 状态 302。 System.Net.HttpStatusCode.Redirect 指示所需的信息位于的位置标头中指定的 URI。 当收到此状态时的默认操作是遵循与响应关联的位置标头。 + // 当原始请求方法是 POST 时,重定向的请求将使用 GET 方法。 + Redirect: 302, + // + // 摘要: + // 等效于 HTTP 状态 303。 System.Net.HttpStatusCode.SeeOther 自动将客户端重定向到的位置标头中指定作为公告的结果的 + // URI。 对指定的位置标头的资源的请求将会执行与 GET。 + SeeOther: 303, + // + // 摘要: + // 等效于 HTTP 状态 303。 System.Net.HttpStatusCode.RedirectMethod 自动将客户端重定向到的位置标头中指定作为公告的结果的 + // URI。 对指定的位置标头的资源的请求将会执行与 GET。 + RedirectMethod: 303, + // + // 摘要: + // 等效于 HTTP 状态 304。 System.Net.HttpStatusCode.NotModified 指示客户端的缓存的副本是最新。 不会传输资源的内容。 + NotModified: 304, + // + // 摘要: + // 等效于 HTTP 状态 305。 System.Net.HttpStatusCode.UseProxy 指示该请求应使用的位置标头中指定的 uri 的代理服务器。 + UseProxy: 305, + // + // 摘要: + // 等效于 HTTP 状态 306。 System.Net.HttpStatusCode.Unused 是对未完全指定的 HTTP/1.1 规范建议的扩展。 + Unused: 306, + // + // 摘要: + // 等效于 HTTP 状态 307。 System.Net.HttpStatusCode.TemporaryRedirect 指示请求信息位于的位置标头中指定的 + // URI。 当收到此状态时的默认操作是遵循与响应关联的位置标头。 当原始请求方法是 POST 时,重定向的请求还将使用 POST 方法。 + TemporaryRedirect: 307, + // + // 摘要: + // 等效于 HTTP 状态 307。 System.Net.HttpStatusCode.RedirectKeepVerb 指示请求信息位于的位置标头中指定的 + // URI。 当收到此状态时的默认操作是遵循与响应关联的位置标头。 当原始请求方法是 POST 时,重定向的请求还将使用 POST 方法。 + RedirectKeepVerb: 307, + // + // 摘要: + // 等效于 HTTP 状态 400。 System.Net.HttpStatusCode.BadRequest 指示无法由服务器理解此请求。 System.Net.HttpStatusCode.BadRequest + // 如果没有其他错误适用,或者如果具体的错误是未知的或不具有其自己的错误代码发送。 + BadRequest: 400, + // + // 摘要: + // 等效于 HTTP 状态 401。 System.Net.HttpStatusCode.Unauthorized 指示所请求的资源需要身份验证。 Www-authenticate + // 标头包含如何执行身份验证的详细信息。 + Unauthorized: 401, + // + // 摘要: + // 等效于 HTTP 状态 402。 System.Net.HttpStatusCode.PaymentRequired 已保留供将来使用。 + PaymentRequired: 402, + // + // 摘要: + // 等效于 HTTP 状态 403。 System.Net.HttpStatusCode.Forbidden 指示服务器拒绝无法完成请求。 + Forbidden: 403, + // + // 摘要: + // 等效于 HTTP 状态 404。 System.Net.HttpStatusCode.NotFound 指示所请求的资源不存在的服务器上。 + NotFound: 404, + // + // 摘要: + // 等效于 HTTP 状态 405。 System.Net.HttpStatusCode.MethodNotAllowed 指示请求方法 (POST 或 GET) + // 不允许对所请求的资源。 + MethodNotAllowed: 405, + // + // 摘要: + // 等效于 HTTP 状态 406。 System.Net.HttpStatusCode.NotAcceptable 表示客户端已指定使用 Accept 标头,它将不接受任何可用的资源表示。 + NotAcceptable: 406, + // + // 摘要: + // 等效于 HTTP 状态 407。 System.Net.HttpStatusCode.ProxyAuthenticationRequired 指示请求的代理要求身份验证。 + // 代理服务器进行身份验证标头包含如何执行身份验证的详细信息。 + ProxyAuthenticationRequired: 407, + // + // 摘要: + // 等效于 HTTP 状态 408。 System.Net.HttpStatusCode.RequestTimeout 指示客户端的服务器预期请求的时间内没有未发送请求。 + RequestTimeout: 408, + // + // 摘要: + // 等效于 HTTP 状态 409。 System.Net.HttpStatusCode.Conflict 指示该请求可能不会执行由于在服务器上发生冲突。 + Conflict: 409, + // + // 摘要: + // 等效于 HTTP 状态 410。 System.Net.HttpStatusCode.Gone 指示所请求的资源不再可用。 + Gone: 410, + // + // 摘要: + // 等效于 HTTP 状态 411。 System.Net.HttpStatusCode.LengthRequired 指示缺少必需的内容长度标头。 + LengthRequired: 411, + // + // 摘要: + // 等效于 HTTP 状态 412。 System.Net.HttpStatusCode.PreconditionFailed 表示失败,此请求的设置的条件,无法执行请求。 + // 使用条件请求标头,如果匹配项,如设置条件无-If-match,或如果-修改-自从。 + PreconditionFailed: 412, + // + // 摘要: + // 等效于 HTTP 状态 413。 System.Net.HttpStatusCode.RequestEntityTooLarge 指示请求来说太大的服务器能够处理。 + RequestEntityTooLarge: 413, + // + // 摘要: + // 等效于 HTTP 状态 414。 System.Net.HttpStatusCode.RequestUriTooLong 指示 URI 太长。 + RequestUriTooLong: 414, + // + // 摘要: + // 等效于 HTTP 状态 415。 System.Net.HttpStatusCode.UnsupportedMediaType 指示该请求是不受支持的类型。 + UnsupportedMediaType: 415, + // + // 摘要: + // 等效于 HTTP 416 状态。 System.Net.HttpStatusCode.RequestedRangeNotSatisfiable 指示从资源请求的数据范围不能返回,或者因为范围的开始处,然后该资源的开头或范围的末尾后在资源的结尾。 + RequestedRangeNotSatisfiable: 416, + // + // 摘要: + // 等效于 HTTP 状态 417。 System.Net.HttpStatusCode.ExpectationFailed 指示无法由服务器满足 Expect + // 标头中给定。 + ExpectationFailed: 417, + // + // 摘要: + // 等效于 HTTP 状态 426。 System.Net.HttpStatusCode.UpgradeRequired 指示客户端应切换到不同的协议,例如 + // TLS/1.0。 + UpgradeRequired: 426, + // + // 摘要: + // 等效于 HTTP 状态 500。 System.Net.HttpStatusCode.InternalServerError 表示在服务器上发生一般性错误。 + InternalServerError: 500, + // + // 摘要: + // 等效于 HTTP 状态 501。 System.Net.HttpStatusCode.NotImplemented 指示服务器不支持所请求的功能。 + NotImplemented: 501, + // + // 摘要: + // 等效于 HTTP 状态 502。 System.Net.HttpStatusCode.BadGateway 指示中间代理服务器从另一个代理或原始服务器接收到错误响应。 + BadGateway: 502, + // + // 摘要: + // 等效于 HTTP 状态 503。 System.Net.HttpStatusCode.ServiceUnavailable 指示将服务器暂时不可用,通常是由于高负载或维护。 + ServiceUnavailable: 503, + // + // 摘要: + // 等效于 HTTP 状态 504。 System.Net.HttpStatusCode.GatewayTimeout 指示中间代理服务器在等待来自另一个代理或原始服务器的响应时已超时。 + GatewayTimeout: 504, + // + // 摘要: + // 等效于 HTTP 状态 505。 System.Net.HttpStatusCode.HttpVersionNotSupported 指示服务器不支持请求的 + // HTTP 版本。 + HttpVersionNotSupported: 505 +} + +export default status \ No newline at end of file diff --git a/Web/src/common/login/index.js b/Web/src/common/login/index.js new file mode 100644 index 0000000..dd853bd --- /dev/null +++ b/Web/src/common/login/index.js @@ -0,0 +1,65 @@ +import { api } from 'common/api' +import { token } from 'common/token' +//import { encryptByDES, decryptByDES } from 'util/des' +import { encryptByRSA } from 'util/rsa' +import { removeGlobal, RSA_PUBLIC_KEY } from 'util/global' +import { message as Message } from 'antd' +import { Redirect } from 'react-router-dom' + +const doLogin = (args) => { + return new Promise((resolve, reject) => { + let { account, password } = args + password = encryptByRSA(password, RSA_PUBLIC_KEY) + api.login({ account, password }).then(({ success, data, message }) => { + if (success) { + token.value = data + Message.success('登录成功') + // if (app.$route.query.return) { + // const r = decryptByDES(app.$route.query.return) + // app.$router.replace(r) + // } else { + // app.$router.replace('/') + // } + return + } else { + Message.error(message) + reject() + } + }).catch(({ message }) => { + if (typeof message === 'object' && message[0]) { + Message.error(message[0].messages[0]) + } + reject() + }) + }) +} + +const doLogout = () => { + return new Promise((resolve, reject) => { + api.logout().then(({ success, message }) => { + if (success) { + removeGlobal() + token.value = '' + // if (app.$route.path === '/') { + // app.$router.replace('/login') + // } else { + // app.$router.replace({ + // path: '/login', + // query: { + // return: decodeURIComponent(encryptByDES(app.$route.path)) + // } + // }) + // } + resolve() + } else { + message.error(message) + reject() + } + }) + }) +} + +export { + doLogin, + doLogout, +} \ No newline at end of file diff --git a/Web/src/common/storage/index.js b/Web/src/common/storage/index.js new file mode 100644 index 0000000..444d5c1 --- /dev/null +++ b/Web/src/common/storage/index.js @@ -0,0 +1,10 @@ +const SESSION_KEY = '__SESSION' +const SETTING_KEY = '__SETTINGS' +const GLOBAL_INFO_KEY = '__GLOBAL_INFO' +const COUNT_DWON_KEY = '__COUNT_DWON' +export { + SESSION_KEY, + SETTING_KEY, + GLOBAL_INFO_KEY, + COUNT_DWON_KEY +} \ No newline at end of file diff --git a/Web/src/common/token/index.js b/Web/src/common/token/index.js new file mode 100644 index 0000000..b489162 --- /dev/null +++ b/Web/src/common/token/index.js @@ -0,0 +1,19 @@ +import { SESSION_KEY } from '../storage' + +const token = { + get value() { + return window.localStorage.getItem(SESSION_KEY) + }, + set value(token) { + if (!token) { + window.localStorage.removeItem(SESSION_KEY) + } else { + window.localStorage.setItem(SESSION_KEY, token) + } + } +} + +export { + SESSION_KEY, + token +} diff --git a/Web/src/components/ant-icon/index.jsx b/Web/src/components/ant-icon/index.jsx new file mode 100644 index 0000000..c2d1e22 --- /dev/null +++ b/Web/src/components/ant-icon/index.jsx @@ -0,0 +1,30 @@ +import React, { Component } from 'react' +import * as Icon from '@ant-design/icons' + +export default class AntIcon extends Component { + render() { + const type = (this.props.type || '').toUpperCase() + + if (type) { + if ( + type.indexOf('OUTLINED') >= 0 || + type.indexOf('FILLED') >= 0 || + type.indexOf('TWOTONE') >= 0 + ) { + const I = Icon[this.props.type] + return I ? : false + } else { + const t = + type + .split('-') + .map(p => { + return p[0] + p.slice(1).toLowerCase() + }) + .join('') + 'Outlined' + const I = Icon[t] + return I ? : false + } + } + return <> + } +} diff --git a/Web/src/components/authority-view/index.jsx b/Web/src/components/authority-view/index.jsx new file mode 100644 index 0000000..0bac500 --- /dev/null +++ b/Web/src/components/authority-view/index.jsx @@ -0,0 +1,257 @@ +import React, { Component } from 'react' +import { Card, Checkbox, Descriptions, Empty, Popover, Spin, Tooltip } from 'antd' +import { AntIcon } from 'components' +import { EMPTY_ID } from 'util/global' + +function generateList(data) { + data.forEach(item => { + if (item.children && item.children.length) { + generateList.call(this, item.children) + } + this.list.push(item) + }) +} + +function getVisible() { + const checked = this.list.filter(item => item.checked) + const caseChildren = checked.filter(item => item.visibleParent || item.type != 2) + const visibleParents = [] + // 递归寻找父级 + const findVisibleParents = children => { + const parents = [] + children.forEach(item => { + if (item.parentId) { + const parent = this.list.find(p => p.id === item.parentId) + if (parent) { + parents.push(parent) + visibleParents.push(parent) + } + } + }) + if (parents.length) { + findVisibleParents(parents) + } + } + + findVisibleParents(caseChildren) + + const checkedIds = checked.map(item => item.id) + const visibleParentsIds = visibleParents.map(item => item.id) + + const result = checkedIds + visibleParentsIds.forEach(item => { + if (!result.includes(item)) { + result.push(item) + } + }) + + return result +} + +function renderDescriptions(data) { + return data.map(item => { + return item.children && item.children.length + ? renderItem.call(this, item) + : renderCheckbox.call(this, item) + }) +} + +function renderItem(data) { + return ( + + this.onChange(e, data)} + > + {data.title} + + } + > + {renderDescriptions.call(this, data.children)} + + + ) +} + +function renderCheckbox(data) { + const grid = ( + + ) + return data.remark ? ( + + {grid} + + ) : ( + grid + ) +} + +export default class AuthorityView extends Component { + state = { + loading: false, + dataSource: [], + } + + list = [] + + constructor(props) { + super(props) + + this.autoLoad = typeof this.props.autoLoad === 'boolean' ? this.props.autoLoad : true + this.loadData = + typeof this.props.loadData === 'function' ? this.props.loadData : async () => {} + } + + /** + * 自动加载数据 + */ + componentDidMount() { + if (this.autoLoad) { + this.onLoadData() + } + } + + onLoadData = async () => { + this.setState({ loading: true }) + + const res = await this.loadData() + + this.list = [] + generateList.call(this, res) + + if (this.props.defaultSelectedKeys) { + this.list.map(item => { + if ( + this.props.defaultSelectedKeys.includes(item.id) && + (!item.children || !item.children.length) + ) { + this.onSelect(true, item) + } + }) + } + + this.setState({ + dataSource: res, + loading: false, + }) + + this.onChange() + } + + onReloadData = () => { + this.onLoadData() + } + + onChange = (e, item) => { + if (e && item) { + this.onSelect(e.target.checked, item) + } + + const visible = getVisible.call(this) + + if (this.props.onSelect) { + this.props.onSelect( + // 返回所有选中 + this.list.filter(p => p.checked).map(p => p.id), + // 返回所有选中和半选 + this.list.filter(p => p.checked || p.indeterminate).map(p => p.id), + // 返回所有选中和半选,但是不返回没有子级选中visibleParent的半选 + visible + ) + } + } + + onSelect = (check, item) => { + item.checked = check + item.indeterminate = false + if (item.children && item.children.length) { + this.onChangeChildren(item.checked, item.children) + } + if (item.parentId) { + this.onChangeParent(item.checked, item.parentId) + } + + this.setState({ + dataSource: this.list.filter(p => p.parentId === EMPTY_ID), + }) + } + + onChangeParent = (checked, parentId) => { + const parent = this.list.find(p => p.id === parentId) + if (parent) { + const checkedCount = parent.children.filter(p => p.checked).length + const indeterminateCount = parent.children.filter(p => p.indeterminate).length + if (checkedCount === parent.children.length) { + // 全选 + parent.checked = true + parent.indeterminate = false + } else if (!checkedCount && !indeterminateCount) { + // 全不选 + parent.checked = false + parent.indeterminate = false + } else { + // 半选 + parent.checked = false + parent.indeterminate = true + } + this.onChangeParent(checked, parent.parentId) + } + } + + onChangeChildren = (checked, children) => { + children.forEach(p => { + p.checked = checked + p.indeterminate = false + if (p.children && p.children.length) { + this.onChangeChildren(checked, p.children) + } + }) + } + + render() { + return ( +
+ }> + {!this.state.loading ? ( + + {this.state.dataSource.map(item => { + return ( + this.onChange(e, item)} + > + {item.title} + + } + > + {renderDescriptions.call(this, item.children)} + + ) + })} + + ) : ( + + )} + +
+ ) + } +} diff --git a/Web/src/components/authorized/handler.js b/Web/src/components/authorized/handler.js new file mode 100644 index 0000000..e17026a --- /dev/null +++ b/Web/src/components/authorized/handler.js @@ -0,0 +1,141 @@ +import store from 'store' + +const { getState } = store + +const stroePath = 'user' + +const authByArray = (auth, permissions) => { + + const flags = [] + + auth.forEach(p => { + switch (p.constructor) { + case String: + flags.push([permissions.includes(p), '&&']) + break + case Array: + flags.push([authByArray(p, permissions), '||']) + break + case Boolean: + flags.push([p, '&&']) + break + default: + break + } + }) + + let result + + flags.forEach((p, i) => { + if (p[1] === '&&') { + if (i === 0) { + result = true + } + if (result) { + result = p[0] + } + } else { + if (i === 0) { + result = false + } + if (!result) { + result = p[0] + } + } + //result = p[1] === '&&' ? result && p[0] : result || p[0] + }) + + return result +} + +const authByJson = (auth, permissions) => { + + let result = true + + const flags = [] + + const deepName = (arr, key) => { + arr.forEach((p, i) => { + switch (p.constructor) { + case String: + arr[i] = `${key}:${p}` + break + case Array: + p = deepName(p, key) + break + default: + break + } + }) + return arr + } + + for (let key in auth) { + const app = auth[key] + switch (app.constructor) { + case String: + flags.push(permissions.includes(`${key}:${app}`)) + break + case Array: + flags.push(authByArray(deepName(app, key), permissions)) + break + default: + break + } + } + + flags.forEach(p => { + result = result && p + }) + + return result + +} + +const auth = (auth, force = false) => { + + let info = this + + if (!info || !Object.keys(info).length) { + info = getState(stroePath) + } + + if (!info) { + return false + } + + /** + * 超级管理员 + */ + if (info.adminType === 1 && !force) { + return true + } + + const permissions = info.permissions + + if (!permissions) { + return false + } + + let flag = false + + if (auth) { + switch (auth.constructor) { + case String: + flag = permissions.includes(auth) + break + case Array: + flag = authByArray(auth, permissions) + break + case Object: + flag = authByJson(auth, permissions) + break + default: + break + } + } + + return flag +} + +export default auth \ No newline at end of file diff --git a/Web/src/components/authorized/index.jsx b/Web/src/components/authorized/index.jsx new file mode 100644 index 0000000..4b75a8e --- /dev/null +++ b/Web/src/components/authorized/index.jsx @@ -0,0 +1,72 @@ +/** + * auth: 允许的权限 + * authExclude: 排除的权限 + * + * auth的几种传值方式 + * 1.String + * 例: auth="sysApp:page" + * 直接传入字符串,对单项权限进行验证 + * + * 2.Array + * 2.1.单项权限 + * 例: auth={['sysApp:page']} + * 2.2.并且关系多项权限 + * 例: auth={['sysApp:page', 'sysApp:add']} + * 数组中传入多个字符串 + * 此时验证的是同时拥有"sysApp:page"和"sysApp:add"两项权限才会渲染 + * 2.3.或者关系多项权限 + * 例: auth={[['sysApp:page', 'sysApp:add'], ['sysApp:edit']]} + * 二维数组结构,内部数组之间为并且关系 + * 此时验证的是"sysApp:page"&"sysApp:add"||"sysApp:edit" + * 注意:或者的条件必须包括在数组中,暴露在外则判定为并且 + * 2.4.可直接传入布尔值 + * 例: auth={['sysApp:page', 1 === 1]} + * auth={[['sysApp:page', 'sysApp:add'], [1 === 1]]} + * + * 3.Json + * 如果觉得多项权限时每次都要写应用编号比较繁琐,可对Array形式进行简化 + * 3.1.单项权限 + * 例: auth={{ sysApp: 'page' }} + * 3.2.并且关系多项权限 + * 例: auth={{ sysApp: ['page', 'add'] }} + * 3.3.或者关系多项权限 + * 例: auth={{ sysApp: [['page', 'add'], ['edit']]}} + * 3.4.可直接传入布尔值 + * 例: auth={{ sysApp: ['page', 1 === 1] }} + * auth={{ sysApp: [['page', 'add'], [1 === 1]] }} + * + */ + +import React, { Component } from 'react' +import store from 'store' +import auth from './handler' + +const { getState, subscribe } = store + +const stroePath = 'user' + +export default class Auth extends Component { + state = getState(stroePath) + + constructor(props) { + super(props) + + this.unsubscribe = subscribe(stroePath, () => { + this.setState(getState(stroePath)) + }) + } + + componentWillUnmount() { + this.unsubscribe() + } + + render() { + const flag = auth.call(this.state, this.props.auth, this.props.force) + + if (flag) { + return this.props.children || <> + } + + return <> + } +} diff --git a/Web/src/components/business/house-log/index.jsx b/Web/src/components/business/house-log/index.jsx new file mode 100644 index 0000000..2fd9638 --- /dev/null +++ b/Web/src/components/business/house-log/index.jsx @@ -0,0 +1,143 @@ +import React, { Component } from 'react' +import { Button, Descriptions, Spin, Steps, Timeline } from 'antd' +import { AntIcon } from 'components' +import { api } from 'common/api' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' + +const ellipsisType = [3, 4, 6] + +export default class houseLog extends Component { + state = { + loading: true, + codes: { + houseLogType: [], + }, + data: [], + ellipsis: true, + } + + ellipsisFlag = [] + + async componentDidMount() { + const { id, infoId, taskId } = this.props + const state = { loading: false } + + state.codes = await getDictData('house_log_type') + + if (id) { + const { data } = await api.houseLogList({ id }) + state.data = data + } else if (infoId) { + const { data } = await api.houseLogListByInfoId({ id: infoId }) + state.data = data + } else if (taskId) { + const { data } = await api.houseLogListByTaskId({ id: taskId }) + state.data = data + } + this.setState(state) + } + + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + render() { + const { loading, data, ellipsis } = this.state + + let button = false + + return ( + } className="h-400-min"> + + {data.map((item, i) => { + let show = true + if ( + ellipsisType.includes(item.type) && + !this.ellipsisFlag.includes(item.type) + ) { + this.ellipsisFlag.push(item.type) + } else if ( + ellipsisType.includes(item.type) && + this.ellipsisFlag.includes(item.type) + ) { + show = false + } + if (show || !ellipsis) { + return ( + + ) : ( + [ + , + , + , + ][item.status] + ) + } + > +
+ + {['等待', '正在', ''][item.status] + + this.bindCodeValue(item.type, 'house_log_type')} + +
+

{item.finishedTime}

+ + {item.remark && ( + {item.remark} + )} + + {item.targetUserNames.split(',').join(' / ')} + + {item.finishedUserName && ( + + {item.finishedUserName} + + )} + +
+ ) + } else if (!button) { + button = true + return ( + } + onClick={() => this.setState({ ellipsis: false })} + /> + } + > +
+
+ ) + } + + return <> + })} +
+
+ ) + } +} diff --git a/Web/src/components/component-dynamic/index.jsx b/Web/src/components/component-dynamic/index.jsx new file mode 100644 index 0000000..45d447c --- /dev/null +++ b/Web/src/components/component-dynamic/index.jsx @@ -0,0 +1,49 @@ +import React, { Component } from 'react' +import { cloneDeep } from 'lodash' + +export default class ComponentDynamic extends Component { + + state = { + key: null, + component: null + } + + componentDidMount() { + this.loadComponent() + } + + async loadComponent() { + let component; + + try { + if (this.props.is) { + if (this.props.is.constructor === Function) { + // 导入函数 + component = await this.props.is() + } else { + // 导入路径,必须是src以下节点,如 pages/home + component = await import(`../../${this.props.is}`) + } + } + } catch { + component = await import(`views/error/404`) + } + + this.setState({ + key: Math.random().toString(16).slice(2), + component: component.default + }) + } + + render() { + + const props = cloneDeep(this.props) + + delete props.is + + if (this.state.component) { + return + } + return <> + } +} \ No newline at end of file diff --git a/Web/src/components/container/index.jsx b/Web/src/components/container/index.jsx new file mode 100644 index 0000000..c2edbdc --- /dev/null +++ b/Web/src/components/container/index.jsx @@ -0,0 +1,23 @@ +import React, { Component } from 'react' + +export default class Container extends Component { + getMode(mode) { + const c = 'container' + const modes = ['xxs', 'xs', 'sm', 'md', 'fluid'] + if (modes.includes(mode)) { + return `${c}-${mode}` + } + return c + } + + render() { + const { mode, className, children } = this.props + + let containerName = this.getMode(mode) + if (className) { + containerName = [containerName, className].join(' ') + } + + return
{children}
+ } +} diff --git a/Web/src/components/form/braft-editor/index.jsx b/Web/src/components/form/braft-editor/index.jsx new file mode 100644 index 0000000..957dd3a --- /dev/null +++ b/Web/src/components/form/braft-editor/index.jsx @@ -0,0 +1,58 @@ +import React, { Component } from 'react' +import BraftEditor from 'braft-editor' +import 'braft-editor/dist/index.css' + +export default class index extends Component { + state = { + editorState: '', + outputHTML: '', + } + + /** + * mount后回调 + */ + componentDidMount() { + this.isLivinig = true + this.toParent() + } + componentWillUnmount() { + this.isLivinig = false + } + handleChange = editorState => { + const outputHTML = editorState.toHTML() + + const { onChange } = this.props + + this.setState({ + editorState: editorState, + outputHTML, + }) + + onChange && onChange(outputHTML) + } + + setEditorContentAsync = () => { + this.isLivinig && + this.setState({ + editorState: BraftEditor.createEditorState(this.props.value), + }) + } + + //给父控件 调用这个方法 因为只有父控件才能掌握页面加载完的时间 + toParent = () => { + this.props.parent.getChildrenMsg(this.setEditorContentAsync) + } + render() { + const { editorState } = this.state + const controls = ['bold', 'italic', 'underline', 'text-color', 'separator'] + + return ( + + ) + } +} diff --git a/Web/src/components/form/color-selector/index.jsx b/Web/src/components/form/color-selector/index.jsx new file mode 100644 index 0000000..3d67ab9 --- /dev/null +++ b/Web/src/components/form/color-selector/index.jsx @@ -0,0 +1,55 @@ +import React, { Component } from 'react' +import { Col, Row, Select } from 'antd' +import { ChromePicker } from 'react-color' + +export default class index extends Component { + select = React.createRef() + + state = { + color: null, + } + + onChange(color) { + this.setState({ color: color.hex }) + } + + onChangeComplete(color) { + this.props.onChange && this.props.onChange(color.hex) + } + + render() { + const { color } = this.state + + const { value } = this.props + + return ( + + + + {options.projects.map(item => ( + + {item.name}({item.note}) + + ))} + + + + + + + + + + + + + + + + + + value.padStart(3, '0')} + max={999} + min={1} + precision={0} + step={1} + className="w-100-p" + placeholder="请输入房屋序号" + /> + + + {showIndustry && -} +
+ + {houseCode && ( + + + {houseCode} + + + )} + 房屋编码说明} + description={ + <> + 房屋所在市 + + + + + —县(市、区) + + + —街道(乡、镇) + + + + —社区、居(村)委会) + + + + —项目 + + + + —实物幢序号 + + + 。 + 根据省厅既有建筑物编号规则,房屋所在区域编号按照市、县(市、区)、街道(乡、镇)、社区、居(村)委会)、项目分类,其中市、县(市)区部分按照《中华人民共和国行政区划代码》(GB2260)标准编码,街道(乡、镇)按《县以下行政区划代码编码规则》(GB10114-88)标准编码,社区、居(村)委会部分按照统计局提供编码设定。各地上报各街道社区名称后,上述编号由系统自动生成。 +
+ 各社区下辖项目由各地负责统一编码,住宅项目序号一般一个小区一号,采用3位数,001号起编,范围为001~999。实物幢序号由各地负责统一编码,以幢为单位,采用3位数,001号起编,范围为001~999。 + + } + /> +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) + } +} diff --git a/Web/src/pages/business/house/code/index.jsx b/Web/src/pages/business/house/code/index.jsx new file mode 100644 index 0000000..e58140a --- /dev/null +++ b/Web/src/pages/business/house/code/index.jsx @@ -0,0 +1,369 @@ +import React, { Component } from 'react' +import { + Button, + Card, + Cascader, + Form, + Input, + Tag, + Popconfirm, + message as Message, + Radio, + Select, + Drawer, +} from 'antd' +import { isEqual } from 'lodash' +import { AntIcon, Auth, Container, HouseLog, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import getDictData from 'util/dic' +import auth from 'components/authorized/handler' +import { toCamelCase } from 'util/format' +import { getSearchInfo } from 'util/query' + +// 配置页面所需接口函数 +const apiAction = { + page: api.houseCodePage, +} + +// 用于弹窗标题 +const name = '房屋编码' + +export default class index extends Component { + state = { + codes: { + houseType: [], + houseIndustry: [], + }, + + options: { + areaTree: [], + }, + + type: '', + + visibleLog: false, + } + + // 表格实例 + table = React.createRef() + + // 表格字段 + columns = [ + { + title: '房屋编码', + dataIndex: 'houseCode', + sorter: true, + width: 400, + render: (text, record) => ( + <> + {`${record.areaName}-${record.roadName}-${record.commName}-${ + record.fullProjName + }-${record.no.toString().padStart(3, '0')}`} +
+ {text} + + ), + }, + { + title: '房屋性质及行业', + dataIndex: 'type', + sorter: true, + width: 150, + render: (text, record) => + this.bindCodeValue(text, 'house_type') + + (text === 2 ? `(${this.bindCodeValue(record.industry, 'house_industry')})` : ''), + }, + { + title: '地址', + dataIndex: 'address', + sorter: true, + }, + { + title: '登记时间', + width: 180, + dataIndex: 'createdTime', + sorter: true, + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ houseCode: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 180, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(record)}>编辑 + + + this.onDelete(record)} + > + 删除 + + + this.onShowLog(record.id)}>日志 + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData('house_type', 'house_industry').then(async res => { + const { data } = await api.getAreaTree() + this.setState( + { + codes: res, + options: { + areaTree: data, + }, + }, + () => { + onLoadData() + } + ) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + if (query.areaCode) { + query.areaCode = query.areaCode.pop() + } + + const searchInfo = getSearchInfo({ + query, + queryType: { + areaCode: '=', + houseCode: 'like', + type: '=', + address: 'like', + }, + }) + + const { data } = await apiAction.page({ + ...params, + searchInfo, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(record) { + const path = 'business/house/code/form' + window.openContentWindow({ + key: record ? record.id : path, + title: record ? '修改房屋编码' : '新增房屋编码', + subTitle: + record && + `${record.areaName}-${record.roadName}-${record.commName}-${record.note}-${record.no + .toString() + .padStart(3, '0')}`, + path, + param: { + id: record && record.id, + }, + }) + // modal.current.open({ + // record + // }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + //#region 自定义方法 + onShowLog(id) { + this.setState({ visibleLog: id }) + } + //#endregion + + render() { + const { options, codes, type, visibleLog } = this.state + + return ( + +
+ + { + if (values.hasOwnProperty('type')) { + this.setState({ type: values.type }) + } + }} + query={ + + + labels.join(' - ')} + fieldNames={{ + label: 'name', + value: 'code', + children: 'children', + }} + options={options.areaTree} + className="w-400" + expandTrigger="hover" + placeholder="请选择所在区域" + /> + + + + + {/* + value && value.padStart(3, '0')} + max={999} + min={1} + precision={0} + step={1} + placeholder="请输入房屋序号" + /> + */} + + + 全部 + {codes.houseType.map(item => ( + + {item.value} + + ))} + + + {type == 2 && ( + + + + )} + + + + + } + operator={ + + + + } + /> + + this.setState({ visibleLog: false })} + destroyOnClose + > + + +
+ ) + } +} diff --git a/Web/src/pages/business/house/company/form.jsx b/Web/src/pages/business/house/company/form.jsx new file mode 100644 index 0000000..80646d0 --- /dev/null +++ b/Web/src/pages/business/house/company/form.jsx @@ -0,0 +1,180 @@ +import React, { Component } from 'react' +import { Button, Col, Form, Input, Row, Select, Spin } from 'antd' +import { AntIcon } from 'components' +import { api } from 'common/api' +import getDictData from 'util/dic' + +const initialValues = {} + +export default class form extends Component { + state = { + // 加载状态 + loading: true, + + codes: { + houseCompanyType: [], + }, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + const state = { loading: false } + //#region 从后端转换成前段所需格式,也可以在此处调用获取详细数据接口 + state.codes = await getDictData('house_company_type') + if (params.id) { + this.record = (await api.houseCompanyDetail({ id: params.id })).data + const { type, info } = this.record + if (type) { + this.record.type = type.split(',') + } + } + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState(state) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + const { type, info } = postData + if (Array.isArray(type)) { + postData.type = type.join(',') + } + //#endregion + return postData + } + } + + //#region 自定义方法 + //#endregion + + render() { + const { codes } = this.state + + return ( +
+ }> +
+ + + + + + +
+ + {(fields, { add, remove }) => ( + <> + {fields.map(({ key, name, fieldKey, ...restField }) => ( + + + remove(name)} + /> + + + + + + + + + + + + + ))} + + + + + )} + +
+
+
+
+ ) + } +} diff --git a/Web/src/pages/business/house/company/index.jsx b/Web/src/pages/business/house/company/index.jsx new file mode 100644 index 0000000..c49000a --- /dev/null +++ b/Web/src/pages/business/house/company/index.jsx @@ -0,0 +1,295 @@ +import React, { Component } from 'react' +import { + Button, + Card, + Descriptions, + Form, + Input, + message as Message, + Popconfirm, + Select, +} from 'antd' +import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import FormBody from './form' + +/** + * 注释段[\/**\/]为必须要改 + */ + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api.houseCompanyPage, + add: api.houseCompanyAdd, + edit: api.houseCompanyEdit, + delete: api.houseCompanyDelete, +} + +/** + * 用于弹窗标题 + * [必要] + */ +const name = '单位' + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = 'houseCompany' + +export default class index extends Component { + state = { + codes: { + houseCompanyType: [], + }, + } + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + columns = [ + { + title: '名称', + width: 300, + dataIndex: 'name', + }, + { + title: '类型', + width: 300, + dataIndex: 'type', + render: text => + text + .split(',') + .map(p => this.bindCodeValue(p, 'house_company_type')) + .join(' / '), + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ [authName]: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, { id }) => ( + + + this.onOpen(this.editForm, id)}>编辑 + + + this.onDelete(id)} + > + 删除 + + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + api.houseCompanyList({ type: '1' }) + getDictData('house_company_type').then(codes => { + this.setState({ codes }, () => { + onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id) { + modal.current.open({ id }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + /** + * 删除 + * @param {*} id + */ + onDelete(id) { + this.onAction(apiAction.delete({ id }), '删除成功') + } + + //#region 自定义方法 + //#endregion + + render() { + const { codes } = this.state + + return ( + +
+ + + + + + + + + + } + operator={ + + + + } + expandedRowRender={record => ( + + {record.info.map((item, i) => ( + + {item.value} + + ))} + + )} + /> + + + + this.table.current.onReloadData()} + > + + + + + + this.table.current.onReloadData()} + > + + + +
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/base/aspect.jsx b/Web/src/pages/business/house/info/form/base/aspect.jsx new file mode 100644 index 0000000..021ac46 --- /dev/null +++ b/Web/src/pages/business/house/info/form/base/aspect.jsx @@ -0,0 +1,230 @@ +import React, { Component } from 'react' +import { Form, Spin, Upload } from 'antd' +import { AntIcon, PhotoPreview } from 'components' +import { cloneDeep, isEqual } from 'lodash' +import { BlobToBase64, GetFileName, PreviewFile } from 'util/file' +import { api } from 'common/api' + +const initialValues = {} + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +export default class aspect extends Component { + state = { + loading: true, + codes: {}, + options: {}, + } + + // 表单实例 + form = React.createRef() + + photoPreview = React.createRef() + + // 初始化数据 + record = {} + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 绑定数据 + */ + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + /** + * DOM加载完成钩子,在此将自身传递给父级 + */ + call() { + if (this.props.onRef) { + this.props.onRef(this) + } + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + if (this.record) { + const { houseInfo } = this.record + const fileValue = [] + const fileList = + !houseInfo.facadePhoto || !houseInfo.facadePhoto.length + ? [] + : houseInfo.facadePhoto.split(',') + for (const fileId of fileList) { + try { + const file = await PreviewFile(fileId) + const base64 = await BlobToBase64(file) + fileValue.push({ + uid: fileId, + response: fileId, + name: file.name, + url: base64, + status: 'done', + }) + } catch { + const { data: file } = await api.sysFileInfoDetail({ id: fileId }) + fileValue.push({ + uid: fileId, + response: '文件已丢失', + name: file.fileOriginName, + status: 'error', + }) + } + } + + houseInfo.facadePhoto = fileValue + } + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + postData.houseInfo.facadePhoto = postData.houseInfo.facadePhoto + .map(item => (item.uid.startsWith('rc-upload') ? item.response : item.uid)) + .join(',') + //#endregion + return postData + } + } + + //#region 自定义方法 + /** + * 表单change事件处理,包括了所有字段的change + * [异步,非必要] + * @param {*} changedValues + * @param {*} allValues + */ + async onValuesChange(changedValues, allValues) {} + + async onFileUpload({ file, onProgress, onSuccess, onError }) { + onProgress({ + percent: 0, + }) + const fd = new FormData() + fd.append('file', file) + try { + const { data: fileId } = await api.sysFileInfoUpload(fd) + onSuccess(fileId) + } catch { + onError() + } + } + + async onFilePreview(file, key) { + const fileList = this.form.current + .getFieldValue(['houseInfo', key]) + .filter(p => p.status === 'done') + const items = [] + for (const _file of fileList) { + const img = new Image() + const src = _file.url || _file.thumbUrl + img.src = src + items.push({ + src, + w: img.naturalWidth, + h: img.naturalHeight, + }) + } + this.photoPreview.current.initPhotoSwipe(items, { + index: fileList.indexOf(file), + }) + } + + async onFileDownload(file) { + const { data, headers } = await api.sysFileInfoDownload({ id: file.response }) + const url = window.URL.createObjectURL(data) + const fileName = GetFileName(headers['content-disposition']) + const a = document.createElement('a') + a.href = url + a.download = fileName + a.click() + window.URL.revokeObjectURL(url) + a.remove() + } + //#endregion + + render() { + const { loading } = this.state + + return ( + }> +
+ this.onValuesChange(changedValues, allValues) + } + > + { + if (Array.isArray(e)) { + return e + } + return e && e.fileList + }} + > + this.onFileUpload(e)} + showUploadList={{ + showRemoveIcon: true, + showDownloadIcon: true, + }} + onPreview={file => this.onFilePreview(file, 'facadePhoto')} + onDownload={file => this.onFileDownload(file)} + > +
+ +
外立面照片
+
+
+
+
+ + +
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/base/attachments.jsx b/Web/src/pages/business/house/info/form/base/attachments.jsx new file mode 100644 index 0000000..b9ac6ce --- /dev/null +++ b/Web/src/pages/business/house/info/form/base/attachments.jsx @@ -0,0 +1,238 @@ +import React, { Component } from 'react' +import { Button, Form, Spin, Upload } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, isEqual } from 'lodash' +import { BlobToBase64, GetFileName, PreviewFile } from 'util/file' +import { api } from 'common/api' + +const initialValues = {} + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +const uploads = [ + { + key: 'anEntryDocument', + label: '立项文件', + }, + { + key: 'planningPermission', + label: '规划许可', + }, + { + key: 'completionRecord', + label: '竣工验收备案', + }, + { + key: 'monitorDocument', + label: '监理文件', + }, + { + key: 'identificationReport', + label: '鉴定报告', + }, + { + key: 'otherDocument', + label: '其他附件', + }, +] + +export default class attachments extends Component { + state = { + loading: true, + codes: {}, + options: {}, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 绑定数据 + */ + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + /** + * DOM加载完成钩子,在此将自身传递给父级 + */ + call() { + if (this.props.onRef) { + this.props.onRef(this) + } + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + if (this.record) { + const { houseInfo } = this.record + const keys = uploads.map(p => p.key) + for (const key of keys) { + const fileValue = [] + const fileList = + !houseInfo[key] || !houseInfo[key].length ? [] : houseInfo[key].split(',') + for (const fileId of fileList) { + try { + const file = await PreviewFile(fileId) + const base64 = await BlobToBase64(file) + fileValue.push({ + uid: fileId, + response: fileId, + name: file.name, + url: base64, + status: 'done', + }) + } catch { + const { data: file } = await api.sysFileInfoDetail({ id: fileId }) + fileValue.push({ + uid: fileId, + response: '文件已丢失', + name: file.fileOriginName, + status: 'error', + }) + } + } + + houseInfo[key] = fileValue + } + } + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + const { houseInfo } = postData + for (const key in houseInfo) { + houseInfo[key] = houseInfo[key] + .map(item => (item.uid.startsWith('rc-upload') ? item.response : item.uid)) + .join(',') + } + //#endregion + return postData + } + } + + //#region 自定义方法 + /** + * 表单change事件处理,包括了所有字段的change + * [异步,非必要] + * @param {*} changedValues + * @param {*} allValues + */ + async onValuesChange(changedValues, allValues) {} + + async onFileUpload({ file, onProgress, onSuccess, onError }) { + onProgress({ + percent: 0, + }) + const fd = new FormData() + fd.append('file', file) + try { + const { data: fileId } = await api.sysFileInfoUpload(fd) + onSuccess(fileId) + } catch { + onError() + } + } + + async onFileDownload(file) { + const { data, headers } = await api.sysFileInfoDownload({ id: file.response }) + const url = window.URL.createObjectURL(data) + const fileName = GetFileName(headers['content-disposition']) + const a = document.createElement('a') + a.href = url + a.download = fileName + a.click() + window.URL.revokeObjectURL(url) + a.remove() + } + //#endregion + + render() { + const { loading } = this.state + + return ( + }> +
+ this.onValuesChange(changedValues, allValues) + } + > + {uploads.map((item, i) => ( + { + if (Array.isArray(e)) { + return e + } + return e && e.fileList + }} + > + this.onFileUpload(e)} + showUploadList={{ + showRemoveIcon: true, + showDownloadIcon: true, + }} + onPreview={() => false} + onDownload={file => this.onFileDownload(file)} + > + + + + ))} +
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/base/building.jsx b/Web/src/pages/business/house/info/form/base/building.jsx new file mode 100644 index 0000000..9977b3d --- /dev/null +++ b/Web/src/pages/business/house/info/form/base/building.jsx @@ -0,0 +1,955 @@ +import React, { Component } from 'react' +import { + Button, + Row, + Col, + Form, + Input, + InputNumber, + Radio, + Checkbox, + Switch, + DatePicker, + Spin, +} from 'antd' +import { cloneDeep, first, isEqual, last, sortBy } from 'lodash' +import { AntIcon } from 'components' +import getDictData from 'util/dic' +import moment from 'moment' +import { CITY } from 'util/global' +import store from 'store' + +const initialValues = { + houseInfo: { + houseUsedStatus: 1, + landAttribute: 1, + curtainWall: 0, + faceBrick: 0, + coating: 0, + painting: 0, + elevator: '0', + }, +} + +const { getState, dispatch } = store + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +const checkboxKeys = ['insulationMaterial', 'wallMaterial'] + +export default class building extends Component { + state = { + loading: true, + codes: { + houseUsedStatus: [], + landAttribute: [], + houseStructureType: [], + houseAseismicGrade: [], + houseBaseInfo: [], + houseInsulationMaterial: [], + houseWallMaterial: [], + houseFireproofGrade: [], + houseBuildingCurtainWall: [], + houseElevator: [], + }, + + showMap: false, + showKeepWarmMaterialText: false, + + theme: getState('layout').theme, + } + + form = React.createRef() + + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + componentWillUnmount() { + dispatch({ + type: 'PATROL_REMOVE_INIT_GRADE_BY_COMPLETED_DATE', + id: this.props.id, + }) + } + + call() { + if (this.props.onRef) { + this.props.onRef(this) + } + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + const _state = { loading: false } + //#region 从后端转换成前段所需格式 + if (this.record) { + const { houseInfo } = this.record + if (houseInfo.completedDate) { + houseInfo.completedDate = moment(houseInfo.completedDate) + dispatch({ + type: 'PATROL_INIT_GRADE_BY_COMPLETED_DATE', + date: { + id: this.props.id, + value: +houseInfo.completedDate.format('YYYY'), + }, + }) + } + + // checkbox + checkboxKeys.forEach(key => { + if (houseInfo[key]) { + houseInfo[key] = houseInfo[key].split(',') + } + }) + + if (houseInfo.insulationMaterial) { + _state.showKeepWarmMaterialText = houseInfo.insulationMaterial.includes('100') + } + } + _state.codes = await getDictData( + 'house_used_status', + 'land_attribute', + 'house_structure_type', + 'house_aseismic_grade', + 'house_base_info', + 'house_insulation_material', + 'house_wall_material', + 'house_fireproof_grade', + 'house_building_curtain_wall', + 'house_elevator' + ) + + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState(_state) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + const { houseInfo } = postData + if (houseInfo.completedDate) { + houseInfo.completedDate = houseInfo.completedDate.format('YYYY-MM-DD') + } + + // checkbox + checkboxKeys.forEach(key => { + if (houseInfo[key]) { + houseInfo[key] = sortBy(houseInfo[key], p => +p).join(',') + } + }) + //#endregion + return postData + } + } + + onValuesChange(changedValues, allValues) { + const form = this.form.current + const { houseInfo } = changedValues + if (houseInfo) { + if ( + houseInfo.hasOwnProperty('landFloorCount') || + houseInfo.hasOwnProperty('underFloorCount') + ) { + const { + houseInfo: { landFloorCount, underFloorCount }, + } = allValues + form.setFieldsValue({ + houseInfo: { + totalFloor: +landFloorCount + +underFloorCount, + }, + }) + } + + if (houseInfo.hasOwnProperty('insulationMaterial')) { + const value = this.checkedNone(houseInfo.insulationMaterial, 'insulationMaterial') + this.setState({ + showKeepWarmMaterialText: value.includes('100'), + }) + } + + if (houseInfo.hasOwnProperty('completedDate')) { + dispatch({ + type: 'PATROL_INIT_GRADE_BY_COMPLETED_DATE', + date: { + id: this.props.id, + value: +houseInfo.completedDate.format('YYYY'), + }, + }) + } + } + } + + //#region 自定义方法 + onShowMap() { + this.setState({ showMap: true }, async () => { + await this.initMap() + const { lng, lat } = this.record.houseCode + const position = [lng, lat] + this.setMarker(position) + this.map.setCenter(position) + }) + } + + initMap() { + // eslint-disable-next-line no-undef + const amap = AMap + + return new Promise(resolve => { + const city = CITY + + const district = new amap.DistrictSearch({ + subdistrict: 0, + extensions: 'all', + level: 'city', + }) + + district.search(city, (status, result) => { + const bounds = result.districtList[0].boundaries, + mask = [] + for (let i = 0; i < bounds.length; i += 1) { + mask.push([bounds[i]]) + } + + const geocoder = new amap.Geocoder({ city }) + geocoder.getLocation(city, (status, result) => { + if (status !== 'complete' || !(result.geocodes && result.geocodes.length)) + return + + this.citycode = result.geocodes[0].addressComponent.citycode + + this.map = new amap.Map(this.refs.map, { + mask, + zoom: 12, + center: result.geocodes[0].location, + mapStyle: `amap://styles/${this.state.theme}`, + }) + + this.map.on('click', e => { + this.setMarker(e.lnglat, geocoder) + }) + + this.map.on('complete', () => { + this.map.setFitView() + this.map.setZoom(12) + + for (const path of bounds) { + new amap.Polyline({ + path, + strokeColor: '#ccc', + strokeWeight: 4, + map: this.map, + }) + } + + resolve() + }) + + const auto = new amap.AutoComplete({ + input: this.refs['map-search'].input, + city, + citylimit: true, + }) + + const placeSearch = new amap.PlaceSearch({ + city, + citylimit: true, + pageSize: 1, + }) + + auto.on('select', ({ poi: { name: keywords, adcode } }) => { + placeSearch.search(keywords, async (status, result) => { + const { + poiList: { pois }, + } = result + for (const poi of pois) { + await this.setMarker(poi.location, geocoder) + this.map.setCenter(poi.location) + } + }) + }) + }) + }) + }) + } + + setMarker(position, geocoder) { + const set = position => { + if (this.marker) { + this.marker.setPosition(position) + } else { + this.marker = new amap.Marker({ + map: this.map, + icon: '//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png', + position, + offset: new amap.Pixel(-13, -30), + }) + } + } + + // eslint-disable-next-line no-undef + const amap = AMap + + return new Promise((resolve, reject) => { + if (geocoder) { + geocoder.getAddress(position, (status, result) => { + if (status === 'complete' && result.regeocode) { + if (result.regeocode.addressComponent.citycode !== this.citycode) { + // 如果选到了别的城市,则中断 + return + } + this.setPosition(result.regeocode.formattedAddress, position) + + set(position) + resolve(position) + } else { + console.error('根据经纬度查询地址失败') + + reject() + } + }) + } else { + set(position) + resolve(position) + } + }) + } + + setPosition(address, { lng, lat }) { + this.form.current.setFieldsValue({ houseCode: { address, lng, lat } }) + } + + checkedNone(value, key) { + const form = this.form.current + if (first(value) == 0 && value.length > 1) { + // 在'无'之后选中其他值 + value.shift() + form.setFieldsValue({ + houseInfo: { [key]: value }, + }) + } else if (last(value) == 0 && value.length > 1) { + // 在其他值之后选中'无' + value = ['0'] + form.setFieldsValue({ + houseInfo: { [key]: value }, + }) + } + return value + } + //#endregion + + render() { + const { loading, codes, showMap, showKeepWarmMaterialText } = this.state + + return ( + }> +
+ this.onValuesChange(changedValues, allValues) + } + > + + + + + + + + + + + + + + + {codes.houseUsedStatus.map(item => ( + + {item.value} + + ))} + + + + + + + {codes.landAttribute.map(item => { + return ( + + {item.value} + + ) + })} + + + + + + + + + + + + + + + + + + + + {showMap ? ( +
+
+ +
+
+
+ ) : ( + + )} +
+ + + {codes.houseStructureType.map(item => { + return ( + + {item.value} + + ) + })} + + + + + + + {codes.houseAseismicGrade.map(item => { + return ( + + {item.value} + + ) + })} + + + + + + + {codes.houseBaseInfo.map(item => { + return ( + + {item.value} + + ) + })} + + + + + + + + {codes.houseInsulationMaterial.map(item => { + return ( + + {item.value} + + ) + })} + + + {showKeepWarmMaterialText && ( + + + + )} + + + {codes.houseWallMaterial.map(item => { + return ( + + {item.value} + + ) + })} + + + + 外墙外保温材料 +
+ 防火等级 + + } + name={['houseInfo', 'fireproofGrade']} + > + + {codes.houseFireproofGrade.map(item => { + return ( + + {item.value} + + ) + })} + +
+ + + {codes.houseBuildingCurtainWall.map(item => { + return ( + + {item.value} + + ) + })} + + + + + + + + + + + + + + + + + + + + + + {codes.houseElevator.map(item => { + return ( + + {item.value} + + ) + })} + + + + + + { + /*$root.transfer.completedYear = date.format('YYYY') */ + }} + className="w-100-p" + placeholder="请选择竣工日期" + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
层;
+
+ + + + + + +
层;
+
+ + + + + + +
+
+ +
+
+ + + + 地上第 + + + + + + + + 层,至 + + + + + + + + 层为商业用房; + + + 地上 + + + + + + + + 层为车棚层; + + + 地上第 + + + + + + + + 层,至 + + + + + + + + 层为住宅 + + + + + + + + + + + + + + + + + + + + + + + + + + + + 单元 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/base/drawing.jsx b/Web/src/pages/business/house/info/form/base/drawing.jsx new file mode 100644 index 0000000..4204403 --- /dev/null +++ b/Web/src/pages/business/house/info/form/base/drawing.jsx @@ -0,0 +1,167 @@ +import React, { Component } from 'react' +import { Checkbox, Col, Form, Input, Row, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, isEqual, sortBy } from 'lodash' +import getDictData from 'util/dic' + +const initialValues = {} + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +export default class drawing extends Component { + state = { + loading: true, + codes: { + houseStorageOfDrawings: [], + }, + options: {}, + + showDrawingMaterialText: false, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * DOM加载完成钩子,绑定数据 + */ + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + /** + * 加载完成,通知父级组件并传递自身 + */ + call() { + const { onRef } = this.props + if (onRef) onRef(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + if (this.record) { + const { houseInfo } = this.record + // checkbox + if (houseInfo.drawingMaterial) { + houseInfo.drawingMaterial = houseInfo.drawingMaterial.split(',') + } + this.setState({ + showDrawingMaterialText: + !!houseInfo.drawingMaterial && houseInfo.drawingMaterial.includes('100'), + }) + } + const codes = await getDictData('house_storage_of_drawings') + console.log(codes) + this.setState({ codes }) + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + const { houseInfo } = postData + // checkbox + if (houseInfo.drawingMaterial) { + houseInfo.drawingMaterial = sortBy(houseInfo.drawingMaterial, p => +p).join(',') + } + //#endregion + return postData + } + } + + //#region 自定义方法 + /** + * 表单change事件处理,包括了所有字段的change + * [异步,非必要] + * @param {*} changedValues + * @param {*} allValues + */ + async onValuesChange(changedValues, allValues) { + const { houseInfo } = changedValues + if (houseInfo.hasOwnProperty('drawingMaterial')) { + this.setState({ showDrawingMaterialText: houseInfo.drawingMaterial.includes('100') }) + } + } + //#endregion + + render() { + const { loading, codes, showDrawingMaterialText } = this.state + + return ( + }> +
+ this.onValuesChange(changedValues, allValues) + } + > + + + {codes.houseStorageOfDrawings.map(item => ( + + {item.value} + + ))} + + + {showDrawingMaterialText && ( + + + + )} +
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/base/identification.jsx b/Web/src/pages/business/house/info/form/base/identification.jsx new file mode 100644 index 0000000..ef818e1 --- /dev/null +++ b/Web/src/pages/business/house/info/form/base/identification.jsx @@ -0,0 +1,155 @@ +import React, { Component } from 'react' +import { Form, Radio, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, isEqual } from 'lodash' +import getDictData from 'util/dic' + +const initialValues = {} + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +export default class identification extends Component { + state = { + loading: true, + codes: { + houseIdentification: [], + houseGovernment: [], + houseUsedStatus: [], + houseGrade: [], + }, + options: {}, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * DOM加载完成钩子,绑定数据 + */ + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + /** + * 加载完成,通知父级组件并传递自身 + */ + call() { + const { onRef } = this.props + if (onRef) onRef(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + const codes = await getDictData( + 'house_identification', + 'house_government', + 'house_used_status', + 'house_grade' + ) + this.setState({ codes }) + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + /** + * 表单change事件处理,包括了所有字段的change + * [异步,非必要] + * @param {*} changedValues + * @param {*} allValues + */ + async onValuesChange(changedValues, allValues) {} + //#endregion + + render() { + const { loading, codes } = this.state + + return ( + }> +
+ this.onValuesChange(changedValues, allValues) + } + > + + + {codes.houseUsedStatus.map(item => ( + + {item.value} + + ))} + + + {/* + + {codes.houseGrade.map(item => ( + + {item.value} + + ))} + + */} +
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/base/index.jsx b/Web/src/pages/business/house/info/form/base/index.jsx new file mode 100644 index 0000000..66a2477 --- /dev/null +++ b/Web/src/pages/business/house/info/form/base/index.jsx @@ -0,0 +1,126 @@ +import React, { Component } from 'react' +import ReactDOM from 'react-dom' +import { Row, Col, Card, Anchor, Spin, Divider } from 'antd' +import { merge } from 'lodash' +import { AntIcon, ComponentDynamic, Container } from 'components' + +const parts = [ + { + title: '建筑物基本信息', + component: () => import('./building'), + }, + { + title: '权属情况', + component: () => import('./ownership'), + }, + // { + // title: '调查情况', + // component: () => import('./investigation'), + // }, + // { + // title: '鉴定治理', + // component: () => import('./identification'), + // }, + { + title: '图纸资料存档处', + component: () => import('./drawing'), + }, + { + title: '相关附件资料', + component: () => import('./attachments'), + }, + { + title: '建筑概貌', + component: () => import('./aspect'), + }, + { + title: '调查单位', + component: () => import('./unit'), + }, +] + +export default class index extends Component { + container = window + + children = [] + + formData = {} + + shouldComponentUpdate(props) { + return this.props.loading !== props.loading + } + + // 通知上层组件已加载完毕 + call(child, index) { + this.children[index] = child + if (this.children.filter(p => p).length === parts.length) { + if (this.props.onRef) { + this.props.onRef(this) + } + } + } + + setContainer = container => { + this.container = (ReactDOM.findDOMNode(container) || {}).parentNode + } + + async getData() { + for (const child of this.children) { + const data = await child.getData() + merge(this.formData, data) + } + + return this.formData + } + + render() { + const { id, loading } = this.props + + return ( + + + +
+
+ + {parts.map((part, i) => ( + +
+ {part.title &&
{part.title}
} + } + wrapperClassName={loading && 'h-400-min'} + > + {!loading && ( + this.call(child, i)} + /> + )} + +
+ {i < parts.length - 1 && } +
+ ))} +
+ + + this.container} + offsetTop={24} + targetOffset={100} + wrapperStyle={{ backgroundColor: 'transparent' }} + onClick={e => e.preventDefault()} + > + {parts.map((part, i) => ( + + ))} + + + + + ) + } +} diff --git a/Web/src/pages/business/house/info/form/base/investigation.jsx b/Web/src/pages/business/house/info/form/base/investigation.jsx new file mode 100644 index 0000000..623a8d1 --- /dev/null +++ b/Web/src/pages/business/house/info/form/base/investigation.jsx @@ -0,0 +1,312 @@ +import React, { Component } from 'react' +import { Checkbox, Form, Input, Radio, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, first, isEqual, last, sortBy } from 'lodash' +import getDictData from 'util/dic' + +const initialValues = { + houseInfo: { + houseSite: ['1'], + adjacentConstruction: ['0'], + chemicalErosion: ['0'], + structuralDismantling: 0, + addingLayer: 0, + repairAndReinforce: ['0'], + historicalCalamity: ['0'], + functionalChange: ['0'], + }, +} + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +const checkboxKeys = [ + 'houseSite', + 'adjacentConstruction', + 'chemicalErosion', + 'repairAndReinforce', + 'historicalCalamity', + 'functionalChange', +] + +export default class investigation extends Component { + state = { + loading: true, + codes: { + houseHouseSite: [], + houseAdjacentConstruction: [], + houseChemicalErosion: [], + houseStructuralDismantling: [], + houseAddingLayer: [], + houseRepairAndReinforce: [], + houseHistoricalCalamity: [], + houseFunctionalChange: [], + }, + options: {}, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * DOM加载完成钩子,绑定数据 + */ + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + /** + * 加载完成,通知父级组件并传递自身 + */ + call() { + const { onRef } = this.props + if (onRef) onRef(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + if (this.record) { + const { houseInfo } = this.record + // checkbox + checkboxKeys.forEach(key => { + if (houseInfo[key]) { + houseInfo[key] = houseInfo[key].split(',') + } + }) + } + const codes = await getDictData( + 'house_house_site', + 'house_adjacent_construction', + 'house_chemical_erosion', + 'house_structural_dismantling', + 'house_adding_layer', + 'house_repair_and_reinforce', + 'house_historical_calamity', + 'house_functional_change' + ) + this.setState({ codes }) + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + const { houseInfo } = postData + // checkbox + checkboxKeys.forEach(key => { + if (houseInfo[key]) { + houseInfo[key] = sortBy(houseInfo[key], p => +p).join(',') + } + }) + //#endregion + return postData + } + } + + //#region 自定义方法 + /** + * 表单change事件处理,包括了所有字段的change + * [异步,非必要] + * @param {*} changedValues + * @param {*} allValues + */ + async onValuesChange(changedValues, allValues) { + const { houseInfo } = changedValues + const key = Object.keys(houseInfo).shift() + if ( + [ + 'adjacentConstruction', + 'chemicalErosion', + 'repairAndReinforce', + 'historicalCalamity', + 'functionalChange', + ].includes(key) + ) { + this.checkedNone(houseInfo[key], key) + } + } + + checkedNone(value, key) { + const form = this.form.current + if (first(value) == 0 && value.length > 1) { + // 在'无'之后选中其他值 + value.shift() + form.setFieldsValue({ + houseInfo: { [key]: value }, + }) + } else if (last(value) == 0 && value.length > 1) { + // 在其他值之后选中'无' + value = ['0'] + form.setFieldsValue({ + houseInfo: { [key]: value }, + }) + } + return value + } + //#endregion + + render() { + const { loading, codes } = this.state + + return ( + }> +
+ this.onValuesChange(changedValues, allValues) + } + > + + + {codes.houseHouseSite.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseAdjacentConstruction.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseChemicalErosion.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseStructuralDismantling.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseAddingLayer.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseRepairAndReinforce.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseHistoricalCalamity.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseFunctionalChange.map(item => ( + + {item.value} + + ))} + + + + + +
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/base/ownership.jsx b/Web/src/pages/business/house/info/form/base/ownership.jsx new file mode 100644 index 0000000..9307940 --- /dev/null +++ b/Web/src/pages/business/house/info/form/base/ownership.jsx @@ -0,0 +1,305 @@ +import React, { Component } from 'react' +import { Col, Form, Input, InputNumber, Radio, Row, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, isEqual } from 'lodash' +import getDictData from 'util/dic' + +const initialValues = { + houseInfo: { + straightHouseCount: 0, + selfHouseCount: 0, + privateHouseCount: 0, + businessCount: 0, + changeHouseCount: 0, + resettlementHouseCount: 0, + otherCount: 0, + houseCount: 0, + }, +} + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +export default class ownership extends Component { + state = { + loading: true, + codes: { + housePropertyRights: [], + }, + options: {}, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * DOM加载完成钩子,绑定数据 + */ + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + /** + * 加载完成,通知父级组件并传递自身 + */ + call() { + const { onRef } = this.props + if (onRef) onRef(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + const codes = await getDictData('house_property_rights') + this.setState({ codes }) + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + /** + * 表单change事件处理,包括了所有字段的change + * [异步,非必要] + * @param {*} changedValues + * @param {*} allValues + */ + async onValuesChange(changedValues, allValues) { + const form = this.form.current + const { houseInfo } = changedValues + + if ( + houseInfo.hasOwnProperty('straightHouseCount') || + houseInfo.hasOwnProperty('selfHouseCount') || + houseInfo.hasOwnProperty('otherCount') || + houseInfo.hasOwnProperty('businessCount') || + houseInfo.hasOwnProperty('changeHouseCount') || + houseInfo.hasOwnProperty('resettlementHouseCount') || + houseInfo.hasOwnProperty('privateHouseCount') + ) { + const { + houseInfo: { + straightHouseCount, + selfHouseCount, + otherCount, + businessCount, + changeHouseCount, + resettlementHouseCount, + privateHouseCount, + }, + } = allValues + form.setFieldsValue({ + houseInfo: { + houseCount: + +straightHouseCount + + +selfHouseCount + + +otherCount + + +businessCount + + +changeHouseCount + + +resettlementHouseCount + + +privateHouseCount, + }, + }) + } + } + //#endregion + + render() { + const { loading, codes } = this.state + + return ( + }> +
+ this.onValuesChange(changedValues, allValues) + } + > + + + {codes.housePropertyRights.map(item => ( + + {item.value} + + ))} + + + + + + + + + +
套;
+
+ + + + + + +
套;
+
+ + + + + + +
套;
+
+ +
+ + + + + + +
套;
+
+ + + + + + +
套;
+
+ + + + + + +
套;
+
+ +
+ + + + + + +
套;
+
+ + + + + + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/base/unit.jsx b/Web/src/pages/business/house/info/form/base/unit.jsx new file mode 100644 index 0000000..59d5bb3 --- /dev/null +++ b/Web/src/pages/business/house/info/form/base/unit.jsx @@ -0,0 +1,154 @@ +import React, { Component } from 'react' +import { Col, Form, Input, Row, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, isEqual } from 'lodash' + +const initialValues = {} + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +export default class unit extends Component { + state = { + loading: true, + codes: {}, + options: {}, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * DOM加载完成钩子,绑定数据 + */ + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + /** + * 加载完成,通知父级组件并传递自身 + */ + call() { + const { onRef } = this.props + if (onRef) onRef(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + /** + * 表单change事件处理,包括了所有字段的change + * [异步,非必要] + * @param {*} changedValues + * @param {*} allValues + */ + async onValuesChange(changedValues, allValues) {} + //#endregion + + render() { + const { loading } = this.state + + return ( + }> +
+ this.onValuesChange(changedValues, allValues) + } + > + + + + + + + + + + + + + + + + + + + + + + +
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/index.jsx b/Web/src/pages/business/house/info/form/index.jsx new file mode 100644 index 0000000..65a255b --- /dev/null +++ b/Web/src/pages/business/house/info/form/index.jsx @@ -0,0 +1,369 @@ +import React, { Component } from 'react' +import { Form, Button, Input, Descriptions, message as Message, Modal, Spin, Tabs } from 'antd' +import { merge, isEqual, pickBy } from 'lodash' +import { AntIcon, ComponentDynamic, Container, Auth } from 'components' +import { api } from 'common/api' + +const tabs = [ + { + title: '房屋基本情况', + component: () => import('./base'), + active: true, + show: true, + }, + // { + // title: '幕墙信息', + // name: 'curtainWall', + // path: 'curtainWall', + // active: false, + // show: false, + // }, + // { + // title: '面砖信息', + // name: 'faceBrick', + // path: 'faceBrick', + // active: false, + // show: false, + // }, + // { + // title: '墙面粉刷信息', + // name: 'whiteWash', + // path: 'whiteWash', + // active: false, + // show: false, + // }, + // { + // title: '墙面涂料信息', + // name: 'coating', + // path: 'coating', + // active: false, + // show: false, + // }, + { + title: '巡查登记', + component: () => import('./patrol'), + active: false, + show: true, + }, +] +const actions = { + save: { + action: 'houseInfoSave', + remark: '保存', + after: 'reload', + }, + submit: { + action: 'houseInfoSubmitToCheck', + remark: '提交', + after: 'close', + }, + check: { + action: 'houseInfoCheck', + remark: '审核', + after: 'close', + }, +} + +export default class index extends Component { + state = { + actived: '0', + + loading: true, + record: null, + + saveDisabled: true, + saving: false, + taskStatus: 0, + } + + children = [] + + formData = {} + checkForm = React.createRef() + + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + componentDidMount() { + // 获取详细数据 + this.onLoadInitData() + } + + call(child, index) { + this.children[index] = child + if (this.children.filter(p => p).length === tabs.filter(p => p.show).length) { + this.setState({ saveDisabled: false }) + } + } + + onLoadInitData() { + this.setState({ + loading: true, + }) + const { taskId } = this.props.param + if (taskId) { + api.houseInfoGetByTaskId({ taskId }).then(({ data }) => { + // 删除空节点 + for (const key in data) { + data[key] = pickBy(data[key], p => p !== null && p !== undefined) + } + this.setState({ + taskStatus: data.patrolInfo.status, + record: data, + loading: false, + }) + }) + } + } + + async onSave() { + await this.onPostData(actions.save) + } + + async onSubmit() { + Modal.confirm({ + content: '确认提交审核吗?', + onOk: () => { + this.onPostData(actions.submit) + }, + onCancel: () => {}, + }) + } + + async onCheck(pass_or_back) { + const form = this.checkForm.current + const valid = await form.validateFields() + + Modal.confirm({ + content: '审核结果即将提交,请确认', + onOk: () => { + if (valid) { + var checkRecord = { + taskCheckRecord: { + taskId: this.props.param.taskId, + passOrBack: +pass_or_back, + content: form.getFieldValue(['taskCheckRecord', 'content']), + }, + } + this.onPostData(actions.check, checkRecord) + } + }, + onCancel: () => {}, + }) + } + + async onPostData(action, append) { + for (const child of this.children) { + try { + const data = await child.getData() + merge(this.formData, data) + } catch (e) { + return e + } + } + + //#region 提交数据 + + if (append) { + this.formData = { + ...this.formData, + ...append, + } + } + if (this.formData.houseCode) { + this.formData.houseCode.id = this.state.record.houseCode.id + } + + if (this.formData.patrolInfo && this.props.param.taskId) { + this.formData.patrolInfo.id = this.props.param.taskId + } + + this.setState({ saving: true }) + + if (action) { + try { + const { success } = await api[action.action](this.formData) + if (success) { + Message.success(action.remark + '成功') + this.setState({ saving: false }) + if (this.props.param.table.current) { + this.props.param.table.current.onReloadData() + } + switch (action.after) { + case 'close': + window.closeContentWindow() + break + default: + this.onLoadInitData() + break + } + } + } finally { + this.setState({ saving: false }) + } + } + + // setTimeout(() => { + // Message.success('提交成功') + // this.setState({ saving: false }) + // }, 3000) + //#endregion + } + + render() { + const { loading, record, saveDisabled, saving } = this.state + + return ( +
+
+ {/* 底部工具栏(需放在前面) */} +
+ +
+ + + {this.state.taskStatus == 3 && ( +
+ + + + + + + +
+ )} +
+
+ + {this.state.taskStatus >= -1 && this.state.taskStatus < 3 && ( + + )} + {this.state.taskStatus == 2 && ( + + )} + + +
+
+
+
+ + }> + + + {record && record.houseCode.areaName} + + + {record && record.houseCode.roadName} + + + {record && record.houseCode.commName} + + + {record && record.houseCode.zoneName} + + + {record && + `${record.houseCode.areaName}-${ + record.houseCode.roadName + }-${record.houseCode.commName}-${ + record.houseCode.fullProjName + }-${record.houseCode.no.toString().padStart(3, '0')}`} + + + {record && record.houseCode.houseCode} + + + + +
+
+ { + this.setState({ actived: activeKey }) + }} + > + {tabs.map((tab, i) => { + if (tab.show) { + return ( + + ) + } + return <> + })} + +
+ {tabs.map((tab, i) => { + if (tab.show) { + return ( +
+ this.call(child, i)} + /> +
+ ) + } + return <> + })} +
+
+
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/patrol/base.jsx b/Web/src/pages/business/house/info/form/patrol/base.jsx new file mode 100644 index 0000000..e231a6d --- /dev/null +++ b/Web/src/pages/business/house/info/form/patrol/base.jsx @@ -0,0 +1,112 @@ +import React, { Component } from 'react' +import { Row, Col, Form, Input, DatePicker, Spin } from 'antd' +import { cloneDeep, isEqual } from 'lodash' +import { AntIcon } from 'components' +import moment from 'moment' +import { CITY } from 'util/global' + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +export default class base extends Component { + state = { + loading: true, + } + + form = React.createRef() + + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + call() { + if (this.props.onRef) { + this.props.onRef(this) + } + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + if (this.record) { + const { patrolDate } = this.record.patrolInfo + this.record.patrolInfo.patrolDate = patrolDate ? moment(patrolDate) : patrolDate + } + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + if (postData.patrolInfo.patrolDate) { + postData.patrolInfo.patrolDate = postData.patrolInfo.patrolDate.format('YYYY-MM-DD') + } + //#endregion + return postData + } + } + + render() { + const { loading } = this.state + return ( + }> +
+ + + + { + /*$root.transfer.completedYear = date.format('YYYY') */ + }} + className="w-100-p" + placeholder="请选择巡查日期" + /> + + + + + + + + +
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/patrol/grade.jsx b/Web/src/pages/business/house/info/form/patrol/grade.jsx new file mode 100644 index 0000000..1cf1c44 --- /dev/null +++ b/Web/src/pages/business/house/info/form/patrol/grade.jsx @@ -0,0 +1,182 @@ +import React, { Component } from 'react' +import { Form, Tooltip, Radio, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, first, isEqual, last, sortBy } from 'lodash' +import getDictData from 'util/dic' +import store from 'store' + +const { getState, subscribe } = store + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +export default class handling extends Component { + state = { + loading: true, + codes: { + housePatrolInitGrade: [], + housePatrolDamageGrade: [], + houseGrade: [], + }, + } + form = React.createRef() + + constructor(props) { + super(props) + + this.unsubscribe = subscribe('business', business => { + const initGrade = this.getInitGrade(business.completedDate) + this.form.current.setFieldsValue({ + patrolInfo: { + initGrade, + }, + }) + }) + } + + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + componentWillUnmount() { + this.unsubscribe() + } + + call() { + if (this.props.onRef) { + this.props.onRef(this) + } + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + + const _state = { loading: false } + //#region 从后端转换成前段所需格式 + if (this.record) { + const { patrolInfo } = this.record + if (this.record.houseInfo.completedDate) + patrolInfo.initGrade = this.getInitGrade(getState('business').completedDate) + } + _state.codes = await getDictData( + 'house_patrol_init_grade', + 'house_patrol_damage_grade', + 'house_grade' + ) + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState(_state) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + + //#endregion + return postData + } + } + + getInitGrade(completedDate) { + const date = completedDate.find(p => p.id === this.props.id) + if (date) { + const { value: year } = date + if (year > 1999) { + return 1 + } + if (year > 1994 && year < 2000) { + return 2 + } + if (year > 1979 && year < 1995) { + return 3 + } + if (year < 1980) { + return 4 + } + } + } + + render() { + const { loading, codes, initGradeValue } = this.state + return ( + }> +
+ + + {codes.housePatrolInitGrade.map(item => { + return ( + + {item.value} + + ) + })} + + + + + + {codes.housePatrolDamageGrade.map(item => { + return ( + + {item.value} + + ) + })} + + + + + + {codes.houseGrade.map(item => { + return ( + + {item.value} + + ) + })} + + +
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/patrol/handling.jsx b/Web/src/pages/business/house/info/form/patrol/handling.jsx new file mode 100644 index 0000000..0bce3c6 --- /dev/null +++ b/Web/src/pages/business/house/info/form/patrol/handling.jsx @@ -0,0 +1,131 @@ +import React, { Component } from 'react' +import { Form, Input, Radio, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, first, isEqual, last, sortBy } from 'lodash' +import getDictData from 'util/dic' + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +export default class handling extends Component { + state = { + loading: true, + codes: { + housePatrolHandlingOpinion: [], + housePatrolRectifyReform: [], + }, + } + form = React.createRef() + + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + call() { + if (this.props.onRef) { + this.props.onRef(this) + } + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + const _state = { loading: false } + //#region 从后端转换成前段所需格式 + _state.codes = await getDictData( + 'house_patrol_handling_opinion', + 'house_patrol_rectify_Reform' + ) + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState(_state) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + + //#endregion + return postData + } + } + render() { + const { loading, codes } = this.state + return ( + }> +
+ + + {codes.housePatrolHandlingOpinion.map(item => { + return ( + + {item.value} + + ) + })} + + + + + + + + + + {codes.housePatrolRectifyReform.map(item => { + return ( + + {item.value} + + ) + })} + + + + + + +
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/patrol/index.jsx b/Web/src/pages/business/house/info/form/patrol/index.jsx new file mode 100644 index 0000000..ac4c4b5 --- /dev/null +++ b/Web/src/pages/business/house/info/form/patrol/index.jsx @@ -0,0 +1,150 @@ +import React, { Component } from 'react' +import ReactDOM from 'react-dom' +import { Anchor, Card, Col, Divider, Row, Spin } from 'antd' +import { AntIcon, ComponentDynamic, Container } from 'components' +import { isEqual, merge } from 'lodash' + +const parts = [ + { + title: '巡查基本情况', + component: () => import('./base'), + }, + { + title: '房屋检查', + component: () => import('./inspection'), + }, + { + title: '等级划分', + component: () => import('./grade'), + }, + { + title: '调查情况', + component: () => import('./investigation'), + }, + { + title: '处理情况', + component: () => import('./handling'), + }, + { + title: '本期巡查结果', + component: () => import('./result'), + }, +] + +export default class index extends Component { + // 子表单实例集合 + children = [] + + // 整合提交数据 + formData = {} + + // 锚点挂载DOM + container = window + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) || this.props.loading !== props.loading + } + + /** + * 加载完成,通知父级组件并传递自身 + */ + call(child, index) { + this.children[index] = child + if (this.children.filter(p => p).length === parts.length) { + const { onRef } = this.props + if (onRef) onRef(this) + } + } + + /** + * 从下级组件获取表单数据,并传递给更上级组件 + * [异步,必要] + * @returns + */ + async getData() { + for (const child of this.children) { + const data = await child.getData() + merge(this.formData, data) + } + return this.formData + } + + /** + * 设置锚点容器 + * [非必要] + * @param {*} container + */ + setContainer = container => { + this.container = (ReactDOM.findDOMNode(container) || {}).parentNode + } + + /** + * 渲染 + * 当前渲染结构已完善,非必要可以不用修改 + * [必要] + * @returns + */ + render() { + const { id, loading } = this.props + + return ( + + + +
+
+ + {parts.map((item, i) => ( + +
+ {item.title &&
{item.title}
} + } + wrapperClassName={loading && 'h-400-min'} + > + {!loading && ( + this.call(child, i)} + /> + )} + +
+ {i < parts.length - 1 && } +
+ ))} +
+ + {/* 锚点,如果不需要可以删除以下节点 */} + + this.container} + offsetTop={24} + targetOffset={100} + wrapperStyle={{ backgroundColor: 'transparent' }} + onClick={e => e.preventDefault()} + > + {parts.map((part, i) => ( + + ))} + + + + + ) + } +} diff --git a/Web/src/pages/business/house/info/form/patrol/inspection.jsx b/Web/src/pages/business/house/info/form/patrol/inspection.jsx new file mode 100644 index 0000000..088f61b --- /dev/null +++ b/Web/src/pages/business/house/info/form/patrol/inspection.jsx @@ -0,0 +1,291 @@ +import React, { Component } from 'react' +import { Col, Form, Input, Row, Spin, Upload } from 'antd' +import { AntIcon, PhotoPreview } from 'components' +import { cloneDeep, isEqual } from 'lodash' +import { BlobToBase64, GetFileName, PreviewFile } from 'util/file' +import { api } from 'common/api' + +const initialValues = {} + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +const imageUploads = [{ key: 'settlementTiltFiles' }, { key: 'otherInfoFiles' }] + +export default class inspection extends Component { + state = { + loading: true, + codes: {}, + options: {}, + } + + // 表单实例 + form = React.createRef() + + photoPreview = React.createRef() + + // 初始化数据 + record = {} + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * DOM加载完成钩子,绑定数据 + */ + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + /** + * 加载完成,通知父级组件并传递自身 + */ + call() { + const { onRef } = this.props + if (onRef) onRef(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + if (this.record) { + const { patrolInfo } = this.record + const keys = imageUploads.map(p => p.key) + for (const key of keys) { + const fileValue = [] + const fileList = + !patrolInfo[key] || !patrolInfo[key].length ? [] : patrolInfo[key].split(',') + for (const fileId of fileList) { + try { + const file = await PreviewFile(fileId) + const base64 = await BlobToBase64(file) + fileValue.push({ + uid: fileId, + response: fileId, + name: file.name, + url: base64, + status: 'done', + }) + } catch { + const { data: file } = await api.sysFileInfoDetail({ id: fileId }) + fileValue.push({ + uid: fileId, + response: '文件已丢失', + name: file.fileOriginName, + status: 'error', + }) + } + } + + patrolInfo[key] = fileValue + } + } + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + const { patrolInfo } = postData + const keys = imageUploads.map(p => p.key) + for (const key of keys) { + patrolInfo[key] = patrolInfo[key] + .map(item => (item.uid.startsWith('rc-upload') ? item.response : item.uid)) + .join(',') + } + //#endregion + return postData + } + } + + //#region 自定义方法 + /** + * 表单change事件处理,包括了所有字段的change + * [异步,非必要] + * @param {*} changedValues + * @param {*} allValues + */ + async onValuesChange(changedValues, allValues) {} + + async onFileUpload({ file, onProgress, onSuccess, onError }) { + onProgress({ + percent: 0, + }) + const fd = new FormData() + fd.append('file', file) + try { + const { data: fileId } = await api.sysFileInfoUpload(fd) + onSuccess(fileId) + } catch { + onError() + } + } + + async onFilePreview(file, key) { + const fileList = this.form.current + .getFieldValue(['patrolInfo', key]) + .filter(p => p.status === 'done') + const items = [] + for (const _file of fileList) { + const img = new Image() + const src = _file.url || _file.thumbUrl + img.src = src + items.push({ + src, + w: img.naturalWidth, + h: img.naturalHeight, + }) + } + this.photoPreview.current.initPhotoSwipe(items, { + index: fileList.indexOf(file), + }) + } + + async onFileDownload(file) { + const { data, headers } = await api.sysFileInfoDownload({ id: file.response }) + const url = window.URL.createObjectURL(data) + const fileName = GetFileName(headers['content-disposition']) + const a = document.createElement('a') + a.href = url + a.download = fileName + a.click() + window.URL.revokeObjectURL(url) + a.remove() + } + //#endregion + + render() { + const { loading } = this.state + + return ( + }> +
+ this.onValuesChange(changedValues, allValues) + } + > + + + + + + + + { + if (Array.isArray(e)) { + return e + } + return e && e.fileList + }} + > + this.onFileUpload(e)} + showUploadList={{ + showRemoveIcon: true, + showDownloadIcon: true, + }} + onPreview={file => + this.onFilePreview(file, 'settlementTiltFiles') + } + onDownload={file => this.onFileDownload(file)} + > +
+ +
沉降倾斜照片
+
+
+
+ + + + + + + + { + if (Array.isArray(e)) { + return e + } + return e && e.fileList + }} + > + this.onFileUpload(e)} + showUploadList={{ + showRemoveIcon: true, + showDownloadIcon: true, + }} + onPreview={file => this.onFilePreview(file, 'otherInfoFiles')} + onDownload={file => this.onFileDownload(file)} + > +
+ +
其他情况照片
+
+
+
+ +
+ + + +
+ + +
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/patrol/investigation.jsx b/Web/src/pages/business/house/info/form/patrol/investigation.jsx new file mode 100644 index 0000000..620f437 --- /dev/null +++ b/Web/src/pages/business/house/info/form/patrol/investigation.jsx @@ -0,0 +1,312 @@ +import React, { Component } from 'react' +import { Checkbox, Form, Input, Radio, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, first, isEqual, last, sortBy } from 'lodash' +import getDictData from 'util/dic' + +const initialValues = { + patrolInfo: { + houseSite: ['1'], + adjacentConstruction: ['0'], + chemicalErosion: ['0'], + structuralDismantling: 0, + addingLayer: 0, + repairAndReinforce: ['0'], + historicalCalamity: ['0'], + functionalChange: ['0'], + }, +} + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +const checkboxKeys = [ + 'houseSite', + 'adjacentConstruction', + 'chemicalErosion', + 'repairAndReinforce', + 'historicalCalamity', + 'functionalChange', +] + +export default class investigation extends Component { + state = { + loading: true, + codes: { + houseHouseSite: [], + houseAdjacentConstruction: [], + houseChemicalErosion: [], + houseStructuralDismantling: [], + houseAddingLayer: [], + houseRepairAndReinforce: [], + houseHistoricalCalamity: [], + houseFunctionalChange: [], + }, + options: {}, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * DOM加载完成钩子,绑定数据 + */ + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + /** + * 加载完成,通知父级组件并传递自身 + */ + call() { + const { onRef } = this.props + if (onRef) onRef(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + if (this.record) { + const { patrolInfo } = this.record + // checkbox + checkboxKeys.forEach(key => { + if (patrolInfo[key]) { + patrolInfo[key] = patrolInfo[key].split(',') + } + }) + } + const codes = await getDictData( + 'house_house_site', + 'house_adjacent_construction', + 'house_chemical_erosion', + 'house_structural_dismantling', + 'house_adding_layer', + 'house_repair_and_reinforce', + 'house_historical_calamity', + 'house_functional_change' + ) + this.setState({ codes }) + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + const { patrolInfo } = postData + // checkbox + checkboxKeys.forEach(key => { + if (patrolInfo[key]) { + patrolInfo[key] = sortBy(patrolInfo[key], p => +p).join(',') + } + }) + //#endregion + return postData + } + } + + //#region 自定义方法 + /** + * 表单change事件处理,包括了所有字段的change + * [异步,非必要] + * @param {*} changedValues + * @param {*} allValues + */ + async onValuesChange(changedValues, allValues) { + const { patrolInfo } = changedValues + const key = Object.keys(patrolInfo).shift() + if ( + [ + 'adjacentConstruction', + 'chemicalErosion', + 'repairAndReinforce', + 'historicalCalamity', + 'functionalChange', + ].includes(key) + ) { + this.checkedNone(patrolInfo[key], key) + } + } + + checkedNone(value, key) { + const form = this.form.current + if (first(value) == 0 && value.length > 1) { + // 在'无'之后选中其他值 + value.shift() + form.setFieldsValue({ + patrolInfo: { [key]: value }, + }) + } else if (last(value) == 0 && value.length > 1) { + // 在其他值之后选中'无' + value = ['0'] + form.setFieldsValue({ + patrolInfo: { [key]: value }, + }) + } + return value + } + //#endregion + + render() { + const { loading, codes } = this.state + + return ( + }> +
+ this.onValuesChange(changedValues, allValues) + } + > + + + {codes.houseHouseSite.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseAdjacentConstruction.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseChemicalErosion.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseStructuralDismantling.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseAddingLayer.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseRepairAndReinforce.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseHistoricalCalamity.map(item => ( + + {item.value} + + ))} + + + + + {codes.houseFunctionalChange.map(item => ( + + {item.value} + + ))} + + + + + +
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/form/patrol/result.jsx b/Web/src/pages/business/house/info/form/patrol/result.jsx new file mode 100644 index 0000000..6fbd040 --- /dev/null +++ b/Web/src/pages/business/house/info/form/patrol/result.jsx @@ -0,0 +1,102 @@ +import React, { Component } from 'react' +import { Form, Input, Radio, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, first, isEqual, last, sortBy } from 'lodash' + +const layout = { + labelCol: { flex: '150px' }, + wrapperCol: { flex: '1' }, +} + +export default class result extends Component { + state = { + loading: true, + codes: { + patrolResult: [ + { code: '0', value: '正常' }, + { code: '-1', value: '异常' }, + ], + }, + } + form = React.createRef() + + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + componentDidMount() { + this.fillData({ + record: this.props.record, + }) + } + + call() { + if (this.props.onRef) { + this.props.onRef(this) + } + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState({ loading: false }) + this.call() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + + //#endregion + return postData + } + } + render() { + const { loading, codes } = this.state + + return ( + }> +
+ + + {codes.patrolResult.map(item => { + return ( + + {item.value} + + ) + })} + + + + + +
+
+ ) + } +} diff --git a/Web/src/pages/business/house/info/index.jsx b/Web/src/pages/business/house/info/index.jsx new file mode 100644 index 0000000..93544ba --- /dev/null +++ b/Web/src/pages/business/house/info/index.jsx @@ -0,0 +1,15 @@ +import React, { Component } from 'react' +import { Button } from 'antd' + +export default class index extends Component { + render() { + return ( +
+ +
+ ) + } +} diff --git a/Web/src/pages/business/house/member/data.jsx b/Web/src/pages/business/house/member/data.jsx new file mode 100644 index 0000000..7a22985 --- /dev/null +++ b/Web/src/pages/business/house/member/data.jsx @@ -0,0 +1,115 @@ +import React, { Component } from 'react' +import { Form, Spin, TreeSelect } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import { api } from 'common/api' + +export default class dataForm extends Component { + state = { + // 加载状态 + loading: true, + + options: { + orgData: [], + areaData: [], + orgCheckedKeys: [], + }, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + id = '' + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + async fillData(params) { + this.id = params.id + //#region 从后端转换成前段所需格式 + const orgData = await this.loadOrgData() + const areaData = await this.loadAreaData() + const orgCheckedKeys = await this.loadMemberOwn(this.id) + this.setState({ + options: { + orgData, + areaData, + orgCheckedKeys, + }, + }) + this.form.current.setFieldsValue({ + id: this.id, + grantOrgIdList: orgCheckedKeys, + grantAreaCodeList: [], + }) + + this.setState({ + loading: false, + }) + } + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.id) { + postData.id = this.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + async loadOrgData() { + const { data } = await api.getOrgTree() + return data + } + + async loadAreaData() { + const { data } = await api.getAreaTree() + return data + } + async loadMemberOwn(id) { + const { data } = await api.houseMemberOwnData({ id }) + return data + } + render() { + return ( +
+ }> +
+ + + + + + +
+
+
+ ) + } +} diff --git a/Web/src/pages/business/house/member/form.jsx b/Web/src/pages/business/house/member/form.jsx new file mode 100644 index 0000000..262d9fd --- /dev/null +++ b/Web/src/pages/business/house/member/form.jsx @@ -0,0 +1,294 @@ +import React, { Component } from 'react' +import { Form, Input, DatePicker, Select, Radio, Spin, TreeSelect } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import { api } from 'common/api' +import moment from 'moment' + +const initialValues = { + sex: 0, + sysEmpParam: {}, +} + +export default class form extends Component { + state = { + // 加载状态 + loading: true, + lockRole: false, + options: { + orgData: [], + roleData: [], + }, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + //#region 从后端转换成前段所需格式 + if (params.id) { + this.record = (await api.houseMemberDetail({ id: params.id })).data + } + const orgData = await this.loadOrgData() + const roleData = await this.LoadRoleData() + + // 日期特殊处理 + if (this.record.birthday) { + this.record.birthday = moment(this.record.birthday) + } + + // 提交的时候是"param",而获取下来却是"info",在这里转换一下 + if (this.record.sysEmpInfo) { + this.record.sysEmpParam = this.record.sysEmpInfo + delete this.record.sysEmpInfo + } else if (!this.record.sysEmpParam) { + this.record.sysEmpParam = { + extIds: [], + } + } + + if (params.orgId) { + this.record.sysEmpParam.orgId = params.orgId + } + const defaultRole = params.id + ? await this.loadOwnRole(params.id) + : await this.loadDefaultRole(params.orgId) + if (defaultRole.constructor === Array) { + this.record.roleId = defaultRole[0] + } else { + this.record.roleId = defaultRole.id + } + const lockRole = this.doLockRole(defaultRole) + + this.setState({ + options: { + orgData, + roleData, + }, + lockRole, + }) + + this.record = { + ...this.record, + } + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false, + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + //console.log(postData) + //#endregion + return postData + } + } + + //#region 自定义方法 + async loadOrgData() { + const { data } = await api.getOrgTree() + return data + } + + async LoadRoleData() { + const { data } = await api.houseMemberDefaultRoleList() + return data + } + async loadOwnRole(id) { + const { data } = await api.houseMemberOwnRole({ id }) + return data + } + async loadDefaultRole(orgId) { + const { data } = await api.houseMemberDefaultRole({ orgId }) + return data + } + async onOrgChange(orgId) { + this.setState({ loading: true }) + const defaultRole = await this.loadDefaultRole(orgId) + const lockRole = this.doLockRole(defaultRole) + this.setState({ loading: false, lockRole }) + } + + doLockRole(defaultRole) { + if (defaultRole.constructor === Array) { + this.form.current.setFieldsValue({ + roleId: defaultRole[0].id, + }) + return true + } else { + this.form.current.setFieldsValue({ + roleId: defaultRole.id, + }) + return defaultRole.code === 'zone_manager' + } + } + + render() { + return ( +
+ }> +
+ + this.onOrgChange(value)} + /> + + + + + + + + + + + {/* {this.props.mode == 'add' && ( + <> + + + + + + + + )} */} + + + + + + + + + + + 保密 + + + + + + + + + + + + + + + + + + + + +
+
+
+ ) + } +} diff --git a/Web/src/pages/business/house/member/index.jsx b/Web/src/pages/business/house/member/index.jsx new file mode 100644 index 0000000..02dd057 --- /dev/null +++ b/Web/src/pages/business/house/member/index.jsx @@ -0,0 +1,417 @@ +import React, { Component } from 'react' +import { + Button, + Card, + Descriptions, + Divider, + Form, + Input, + List, + message as Message, + Popconfirm, + Select, + Switch, + Tag, +} from 'antd' +import { + AntIcon, + Auth, + Container, + Image, + ModalForm, + QueryList, + QueryTableActions, + QueryTreeLayout, +} from 'components' +import { api } from 'common/api' +import { toCamelCase } from 'util/format' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import FormBody from './form' +import Selector from './selector' +import DataForm from './data' + +// 配置页面所需接口函数 +const apiAction = { + tree: api.getOrgTree, + page: api.houseMemberPage, + add: api.houseMemberAdd, + edit: api.houseMemberEdit, + delete: api.houseMemberDelete, + + changeStatus: api.houseMemberChangeStatus, + resetPwd: api.sysUserResetPwd, + + grantData: api.houseMemberGrantData, +} + +// 用于弹窗标题 +const name = '人员' + +export default class index extends Component { + state = { + codes: { + sex: [], + commonStatus: [], + }, + } + + // 表格实例 + list = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + dataForm = React.createRef() + // 树选中节点 + selectId = undefined + + selectorModal = React.createRef() + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + this.list.current.onLoading() + getDictData('sex', 'common_status').then(codes => { + this.setState({ codes }, () => { + this.list.current.onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + query = { + ...query, + sysEmpParam: { + orgId: this.selectId, + }, + } + + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 调用树结构数据接口 + * [异步,必要] + * @returns + */ + loadTreeData = async () => { + const { data } = await apiAction.tree() + return data + } + + /** + * 树节点选中事件 + * [必要] + * @param {*} id + */ + onSelectTree(id) { + this.selectId = id + this.list.current.onReloadData() + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id) { + modal.current.open({ + orgId: this.selectId, + id, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + this.list.current.onLoading() + try { + await action + Message.success(successMessage) + this.list.current.onReloadData() + } catch { + this.list.current.onLoaded() + } + } + + /** + * 删除 + * @param {*} id + */ + onDelete(id) { + this.onAction(apiAction.delete({ id }), '删除成功') + } + + //#region 自定义方法 + renderItem(record) { + const { + id, + account, + name, + nickName, + avatar, + sex, + phone, + email, + status, + roleCode, + roleName, + orgName, + } = record + return ( + + + this.onOpen(this.editForm, id)}>编辑 + + + this.onDelete(id)} + > + 删除 + + + + this.onResetPassword(id)}>重置密码 + + + this.onOpen(this.dataForm, id)}>授权额外数据 + + , + ]} + > + + } + /> + {roleCode && roleCode.includes('house_security_manager') && ( + + )} + + } + title={ + <> + {nickName || name} + {roleName && + roleName.split(',').map((item, i) => ( + + + {item} + + ))} + + } + description={account} + /> + + {orgName} + + {this.bindCodeValue(sex, 'sex')} + + {phone || '未设置'} + {email || '未设置'} + +
+ +
+ this.onSetUserStatus(id, checked)} + /> +
+
+
+
+ ) + } + + onSetUserStatus(id, checked) { + this.onAction( + apiAction.changeStatus({ + id, + status: +!checked, + }), + '设置成功' + ) + } + + onResetPassword(id) { + this.onAction(apiAction.resetPwd({ id }), '重置成功') + } + //#endregion + + render() { + return ( + this.onSelectTree(key)} + > + + + + + + + + + + + + + + } + operator={ + + } + renderItem={record => this.renderItem(record)} + /> + + + + this.list.current.onReloadData()} + > + + + + this.list.current.onReloadData()} + > + + + + this.list.current.onReloadData()} + > + + + + + + + + ) + } +} diff --git a/Web/src/pages/business/house/member/selector/index.jsx b/Web/src/pages/business/house/member/selector/index.jsx new file mode 100644 index 0000000..13bbe4e --- /dev/null +++ b/Web/src/pages/business/house/member/selector/index.jsx @@ -0,0 +1,66 @@ +import React, { Component } from 'react' +import { Tabs } from 'antd' +import SelectorList from './selector-list' +import SelectedList from './selected-list' + +export default class index extends Component { + state = { + userId: '', + } + + selectorList = React.createRef() + selectedList = React.createRef() + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.setState({ + userId: params.id, + }) + } + + //#region 自定义方法 + onReloadAll() { + if (this.selectorList.current) { + this.selectorList.current.table.current.onReloadData() + } + if (this.selectedList.current) { + this.selectedList.current.table.current.onReloadData() + } + } + //#endregion + + render() { + const { userId } = this.state + + return ( + + + this.onReloadAll()} + /> + + + this.onReloadAll()} + /> + + + ) + } +} diff --git a/Web/src/pages/business/house/member/selector/selected-list.jsx b/Web/src/pages/business/house/member/selector/selected-list.jsx new file mode 100644 index 0000000..8c36e4c --- /dev/null +++ b/Web/src/pages/business/house/member/selector/selected-list.jsx @@ -0,0 +1,263 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, InputNumber, message as Message, Radio, Select } from 'antd' +import { AntIcon, Auth, Container, QueryTable } from 'components' +import { api } from 'common/api' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import { getSearchInfo } from 'util/query' + +/** + * 注释段[\/**\/]为必须要改 + */ + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api.houseSelectedPage, +} + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = 'houseSelector' + +export default class index extends Component { + state = { + codes: { + houseType: [], + houseIndustry: [], + }, + + saving: false, + + type: '', + + selectedRowKeys: [], + } + + // 表格实例 + table = React.createRef() + + columns = [ + { + title: '房屋编码', + dataIndex: 'houseCode', + sorter: true, + width: 300, + render: (text, record) => + `${record.areaName}-${record.roadName}-${record.commName}-${ + record.fullProjName + }-${record.no.toString().padStart(3, '0')}`, + }, + { + title: '房屋性质及行业', + dataIndex: 'type', + sorter: true, + width: 150, + render: text => this.bindCodeValue(text, 'house_type'), + }, + { + title: '地址', + dataIndex: 'address', + sorter: true, + }, + { + title: '登记时间', + dataIndex: 'createdTime', + sorter: true, + width: 150, + }, + ] + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData('house_type', 'house_industry').then(codes => { + this.setState({ codes }, () => { + onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const searchInfo = getSearchInfo({ + query, + queryType: { type: '=' }, + }) + + const { data } = await apiAction.page({ + ...params, + searchInfo, + userId: this.props.userId, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code === code) + if (c) { + return c.value + } + } + return null + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + //#region 自定义方法 + async onHouseSelectRevoke() { + const { selectedRowKeys } = this.state + const { userId, onReloadAll } = this.props + this.setState({ saving: true }) + await this.onAction( + api.houseSelectRevoke({ + ids: selectedRowKeys, + userId, + }), + '撤销成功' + ) + this.setState({ + saving: false, + selectedRowKeys: [], + }) + if (onReloadAll) { + onReloadAll() + } + } + //#endregion + + render() { + const { codes, saving, type, selectedRowKeys } = this.state + + return ( + + this.setState({ selectedRowKeys }), + }} + queryInitialValues={{ + type: '', + }} + onQueryChange={values => { + if (values.hasOwnProperty('type')) { + this.setState({ type: values.type }) + } + }} + query={ + + + value && value.padStart(3, '0')} + max={999} + min={1} + precision={0} + step={1} + placeholder="请输入房屋序号" + /> + + + + 全部 + {codes.houseType.map(item => ( + + {item.value} + + ))} + + + {type == 2 && ( + + + + )} + + + + + + + + } + operator={ + + + + } + /> + + ) + } +} diff --git a/Web/src/pages/business/house/member/selector/selector-list.jsx b/Web/src/pages/business/house/member/selector/selector-list.jsx new file mode 100644 index 0000000..3a9e355 --- /dev/null +++ b/Web/src/pages/business/house/member/selector/selector-list.jsx @@ -0,0 +1,263 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, InputNumber, message as Message, Radio, Select } from 'antd' +import { AntIcon, Auth, Container, QueryTable } from 'components' +import { api } from 'common/api' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import { getSearchInfo } from 'util/query' + +/** + * 注释段[\/**\/]为必须要改 + */ + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api.houseSelectorPage, +} + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = 'houseSelector' + +export default class index extends Component { + state = { + codes: { + houseType: [], + houseIndustry: [], + }, + + saving: false, + + type: '', + + selectedRowKeys: [], + } + + // 表格实例 + table = React.createRef() + + columns = [ + { + title: '房屋编码', + dataIndex: 'houseCode', + sorter: true, + width: 300, + render: (text, record) => + `${record.areaName}-${record.roadName}-${record.commName}-${ + record.fullProjName + }-${record.no.toString().padStart(3, '0')}`, + }, + { + title: '房屋性质及行业', + dataIndex: 'type', + sorter: true, + width: 150, + render: text => this.bindCodeValue(text, 'house_type'), + }, + { + title: '地址', + dataIndex: 'address', + sorter: true, + }, + { + title: '登记时间', + dataIndex: 'createdTime', + sorter: true, + width: 150, + }, + ] + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData('house_type', 'house_industry').then(codes => { + this.setState({ codes }, () => { + onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const searchInfo = getSearchInfo({ + query, + queryType: { type: '=' }, + }) + + const { data } = await apiAction.page({ + ...params, + searchInfo, + userId: this.props.userId, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code === code) + if (c) { + return c.value + } + } + return null + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + //#region 自定义方法 + async onHouseSelect() { + const { selectedRowKeys } = this.state + const { userId, onReloadAll } = this.props + this.setState({ saving: true }) + await this.onAction( + api.houseSelect({ + ids: selectedRowKeys, + userId, + }), + '选房成功' + ) + this.setState({ + saving: false, + selectedRowKeys: [], + }) + if (onReloadAll) { + onReloadAll() + } + } + //#endregion + + render() { + const { codes, saving, type, selectedRowKeys } = this.state + + return ( + + this.setState({ selectedRowKeys }), + }} + queryInitialValues={{ + type: '', + }} + onQueryChange={values => { + if (values.hasOwnProperty('type')) { + this.setState({ type: values.type }) + } + }} + query={ + + + value && value.padStart(3, '0')} + max={999} + min={1} + precision={0} + step={1} + placeholder="请输入房屋序号" + /> + + + + 全部 + {codes.houseType.map(item => ( + + {item.value} + + ))} + + + {type == 2 && ( + + + + )} + + + + + + + + } + operator={ + + + + } + /> + + ) + } +} diff --git a/Web/src/pages/business/house/project/form.jsx b/Web/src/pages/business/house/project/form.jsx new file mode 100644 index 0000000..cee85f4 --- /dev/null +++ b/Web/src/pages/business/house/project/form.jsx @@ -0,0 +1,273 @@ +import React, { Component } from 'react' +import { Cascader, Form, Input, InputNumber, Radio, Spin, TreeSelect } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import { api } from 'common/api' +import { numberToChinese } from 'util/format' + +const initialValues = { + sort: 100, + type: 1, +} +export default class form extends Component { + state = { + // 加载状态 + loading: true, + exist: false, + + options: { + areaData: [], + }, + } + areaCode = '' + houseType = 1 + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + initRecord = {} + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + let areaCodeDefault = params.pid ? params.pid : '' + this.houseType = params.record ? params.record.type : 1 + if (params.id) { + this.setState({ + loading: true, + }) + + api.houseProjectGetById({ projectId: params.id }).then(({ data }) => { + areaCodeDefault = data.areaCode + this.record = data + + this.setState({ + loading: false, + }) + }) + } + // this.record = cloneDeep(params.record) + this.initRecord = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + const areaData = await this.loadAreaData() + + this.setState({ + exist: !!params.id, + options: { areaData }, + }) + + const areaCode = [] + const findCode = (data, level) => { + level = level || 0 + for (let i = 0; i < data.length; i++) { + const item = data[i] + areaCode[level] = item.code + + if (item.code === areaCodeDefault) { + areaCode.length = level + 1 + return true + } + + if (item.children && item.children.length) { + const found = findCode(item.children, level + 1) + if (found) { + return true + } + } + } + } + + if (areaCodeDefault) { + findCode(areaData) + this.areaCode = areaCodeDefault + if (!this.state.exist) { + this.nextSort(this.areaCode, this.houseType) + } + } + + this.record = { + pid: params.pid, + ...this.record, + areaCode: + areaCode.length > 0 && areaCode[areaCode.length - 1].length == 12 ? areaCode : [], + } + //#endregion + + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false, + }) + } + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + postData.areaCode = postData.areaCode[postData.areaCode.length - 1] + //#endregion + return postData + } + } + + async loadAreaData() { + const { data } = await api.getAreaTree() + console.log(data) + const clearChiildren = data => { + data.forEach(item => { + if (item.children && item.children.length) { + clearChiildren(item.children) + } else { + delete item.children + } + }) + } + clearChiildren(data) + return data + } + + async nextSort(areaCode, houseType) { + this.loading = true + if ( + !!this.initRecord && + this.initRecord.areaCode == areaCode && + this.initRecord.type == houseType + ) { + this.form.current.setFieldsValue({ + name: this.initRecord.name, + sort: this.initRecord.sort, + }) + } else if (areaCode.length < 12) { + this.form.current.setFieldsValue({ + name: '', + sort: 0, + areaCode: [], + }) + } else { + await api + .houseProjectNextSort({ areaCode, type: houseType }) + .then(({ data }) => { + this.form.current.setFieldsValue({ + name: `项目${numberToChinese(data)}`, + sort: data, + }) + }) + .catch(() => { + this.form.current.setFieldsValue({ + name: '', + sort: 0, + areaCode: [], + }) + }) + .finally(() => { + this.loading = false + }) + } + } + + onHouseTypeChange(e) { + this.houseType = e.target.value + if (this.areaCode != '') { + this.nextSort(this.areaCode, this.houseType) + } + } + + onAreaCodeChange(value) { + this.areaCode = value[value.length - 1] + if (this.houseType > 0) { + this.nextSort(this.areaCode, this.houseType) + } + } + + render() { + return ( +
+ }> +
+ + this.onHouseTypeChange(e)} + > + + 住宅 + + + 非住宅 + + + + + this.onAreaCodeChange(val)} + /> + + + + + + + + + + +
+
+
+ ) + } +} diff --git a/Web/src/pages/business/house/project/index.jsx b/Web/src/pages/business/house/project/index.jsx new file mode 100644 index 0000000..1f6d8f7 --- /dev/null +++ b/Web/src/pages/business/house/project/index.jsx @@ -0,0 +1,313 @@ +import React, { Component } from 'react' +import { Button, Radio, Card, Form, Input, message as Message, Popconfirm } from 'antd' +import { + AntIcon, + Auth, + Container, + ModalForm, + QueryTable, + QueryTableActions, + QueryTreeLayout, +} from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { toCamelCase } from 'util/format' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import FormBody from './form' + +const apiAction = { + tree: api.getAreaTree, + page: api.getHouseProjectPage, + add: api.houseProejctAdd, + edit: api.houseProejctEdit, + delete: api.houseProejctDelete, +} + +const name = '项目' + +export default class index extends Component { + state = { + codes: { + houseType: [], + }, + type: 1, + } + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + // 树选中节点 + selectCode = undefined + columns = [ + { + title: '项目名称', + dataIndex: 'name', + width: 150, + sorter: true, + }, + { + title: '社区', + dataIndex: 'areaName', + width: 100, + sorter: true, + }, + { + title: '备注', + dataIndex: 'note', + width: 150, + sorter: true, + }, + { + title: '类型', + dataIndex: 'type', + sorter: true, + width: 80, + render: text => <>{this.bindCodeValue(text, 'house_type')}, + }, + ] + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ houseProjectInfo: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(this.editForm, record.id)}>编辑 + + + this.onDelete(record)} + > + 删除 + + + + ), + }) + } + } + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + this.table.current.onLoading() + getDictData('house_type').then(res => { + this.setState( + { + codes: res, + }, + () => { + this.table.current.onLoadData() + } + ) + }) + } + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + query = { + ...query, + pid: this.selectCode, + } + //首次加载根据code列升序排序 + // if (!params.sortField) { + // params.sortField = 'code'; + // params.sortOrder = 'ascend'; + // } + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 调用树结构数据接口 + * [异步,必要] + * @returns + */ + loadTreeData = async () => { + const { data } = await apiAction.tree() + return data + } + + /** + * 树节点选中事件 + * [必要] + * @param {*} id + */ + onSelectTree(code) { + this.selectCode = code + this.table.current.onReloadData() + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => +p.code === code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(modal, id) { + modal.current.open({ + pid: this.selectCode, + // record, + id, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + this.table.current.onLoading() + try { + await action + Message.success(successMessage) + this.table.current.onReloadData() + } catch { + this.table.current.onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + render() { + return ( + this.onSelectTree(key)} + replaceFields={{ value: 'code', title: 'name', children: 'children' }} + > + + + { + if (values.hasOwnProperty('type')) { + this.setState({ type: values.type }) + } + }} + query={ + + + + 全部 + + 住宅 + + + 非住宅 + + + + + + + + + + + } + operator={ + + + + } + > + + + this.table.current.onReloadData()} + > + + + + this.table.current.onReloadData()} + > + + + + ) + } +} diff --git a/Web/src/pages/business/house/query/detail.jsx b/Web/src/pages/business/house/query/detail.jsx new file mode 100644 index 0000000..60c4d18 --- /dev/null +++ b/Web/src/pages/business/house/query/detail.jsx @@ -0,0 +1,35 @@ +import React, { Component } from 'react' +import { Card } from 'antd' +import Container from 'components/container' +import { api } from 'common/api' +import ReactJson from 'react-json-view' + +export default class detail extends Component { + state = { + loading: false, + record: null, + } + + componentDidMount() { + // 获取详细数据 + const { id } = this.props.param + if (id) { + api.houseQueryDetail({ id }).then(({ data }) => { + this.setState({ + record: data, + loading: false, + }) + }) + } + } + + render() { + return ( + + + + + + ) + } +} diff --git a/Web/src/pages/business/house/query/index.jsx b/Web/src/pages/business/house/query/index.jsx new file mode 100644 index 0000000..937dc5b --- /dev/null +++ b/Web/src/pages/business/house/query/index.jsx @@ -0,0 +1,571 @@ +import React, { Component } from 'react' +import { + Button, + Card, + Checkbox, + Col, + DatePicker, + Form, + Input, + InputNumber, + message as Message, + Row, + Tag, +} from 'antd' +import { + AntIcon, + Auth, + Container, + InputNumberRange, + QueryTable, + QueryTableActions, +} from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { first, isEqual, last } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import { getSearchDateRange, getSearchInfo, QueryType } from 'util/query' + +/** + * 注释段[\/**\/]为必须要改 + */ + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api.houseQueryPage, +} + +/** + * 用于弹窗标题 + * [必要] + */ +const name = '/**/' + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = 'houseQuery' + +export default class index extends Component { + state = { + codes: { + houseStatus: [], + houseType: [], + houseIndustry: [], + houseUsedStatus: [], + housePropertyRights: [], + landAttribute: [], + houseBaseInfo: [], + houseStructureType: [], + houseStorageOfDrawings: [], + houseGrade: [], + }, + + showDrawingMaterialText: false, + } + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + columns = [ + { + title: '房屋编码', + dataIndex: 'houseCode', + sorter: true, + width: 300, + render: (text, record) => ( + <> + {`${record.areaName}-${record.roadName}-${record.commName}-${ + record.note + }-${record.no.toString().padStart(3, '0')}`} +
+ {text} + + ), + }, + { + title: '房屋性质及行业', + dataIndex: 'type', + sorter: true, + width: 150, + render: text => this.bindCodeValue(text, 'house_type'), + }, + { + title: '地址', + dataIndex: 'address', + sorter: true, + }, + { + title: '建档状态', + dataIndex: 'state', + sorter: true, + width: 100, + render: text => this.bindCodeValue(text, 'house_status'), + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ [authName]: 'detail' }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(record.id)}>查看 + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData( + 'house_status', + 'house_type', + 'house_industry', + 'house_used_status', + 'house_property_rights', + 'land_attribute', + 'house_base_info', + 'house_structure_type', + 'house_storage_of_drawings', + 'house_grade' + ).then(codes => { + this.setState({ codes }, () => { + onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + query.completedDate = getSearchDateRange(query.completedDate) + query.createdTime = getSearchDateRange(query.createdTime) + + const searchInfo = getSearchInfo({ + query, + queryType: { + areaCode: QueryType.Like, + completedDate: [QueryType.GreaterThanOrEqual, QueryType.LessThan], + createdTime: [QueryType.GreaterThanOrEqual, QueryType.LessThan], + totalArea: [QueryType.GreaterThanOrEqual, QueryType.LessThanOrEqual], + totalFloor: [QueryType.GreaterThanOrEqual, QueryType.LessThanOrEqual], + }, + }) + + const { data } = await apiAction.page({ + ...params, + searchInfo, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(id) { + window.openContentWindow({ + title: '房屋详情', + path: 'business/house/query/detail', + param: { id }, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + /** + * 删除 + * @param {*} id + */ + onDelete(id) { + this.onAction(apiAction.delete({ id }), '删除成功') + } + + //#region 自定义方法 + rednerMoreQuery() { + const { codes, showDrawingMaterialText } = this.state + + return ( + + + + + {codes.houseUsedStatus.map(item => ( + + {item.value} + + ))} + + + + + + + 全部 + + + + + + + + + 全部 + + + + + + + + + 全部 + + + + + + + + + 全部 + + + + + + + + + + + + + + + + + + + 全部 + {codes.housePropertyRights.map(item => ( + + {item.value} + + ))} + + + + + + + 全部 + {codes.landAttribute.map(item => ( + + {item.value} + + ))} + + + + + + + 全部 + {codes.houseBaseInfo.map(item => ( + + {item.value} + + ))} + + + + + + + 全部 + {codes.houseStructureType.map(item => ( + + {item.value} + + ))} + + + + + + + + {codes.houseStorageOfDrawings.map(item => ( + + {item.value} + + ))} + + + {showDrawingMaterialText && ( + + + + )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 全部 + {codes.houseGrade.map(item => ( + + {item.value} + + ))} + + + + + ) + } + + onQueryChange(changedValues, allValues) { + console.log(changedValues) + if (changedValues.hasOwnProperty('drawingMaterial') && changedValues.drawingMaterial) { + this.setState({ + showDrawingMaterialText: changedValues.drawingMaterial.includes('100'), + }) + return + } + + // 全部 + const { codes } = this.state + const key = first(Object.keys(changedValues)) + const mapCount = { + curtainWall: 2, + faceBrick: 2, + coating: 2, + painting: 2, + propertyRights: codes.housePropertyRights.length, + landAttribute: codes.landAttribute.length, + baseInfo: codes.houseBaseInfo.length, + structureType: codes.houseStructureType.length, + houseGrade: codes.houseGrade.length, + } + if (Object.keys(mapCount).includes(key)) { + return { + [key]: this.checkedNone(changedValues[key], mapCount[key]), + } + } + } + + checkedNone(value, count) { + if (first(value) == '' && value.length > 1) { + // 在'无'之后选中其他值 + value.shift() + } else if ((last(value) == '' && value.length > 1) || value.length === count) { + // 在其他值之后选中'无' + value = [''] + } + return value + } + //#endregion + + render() { + const { codes, type } = this.state + + return ( + +
+ + + + + + + + + + } + moreQuery={ + {this.rednerMoreQuery()} + } + onQueryChange={(changedValues, allValues) => + this.onQueryChange(changedValues, allValues) + } + operator={ + + + + } + /> + +
+ ) + } +} diff --git a/Web/src/pages/business/house/task/check/index.jsx b/Web/src/pages/business/house/task/check/index.jsx new file mode 100644 index 0000000..dd804ff --- /dev/null +++ b/Web/src/pages/business/house/task/check/index.jsx @@ -0,0 +1,338 @@ +import React, { Component } from 'react' +import { + Button, + Card, + Drawer, + Form, + Input, + message as Message, + Popconfirm, + Radio, + Select, + Tag, +} from 'antd' +import { + AntIcon, + Auth, + Container, + HouseLog, + ModalForm, + QueryTable, + QueryTableActions, +} from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import { getSearchInfo } from 'util/query' + +/** + * 注释段[\/**\/]为必须要改 + */ + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api.houseTaskPage, +} + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = 'houseTask' + +export default class index extends Component { + state = { + codes: { + status: [ + { code: 3, value: '审核中' }, + { code: 6, value: '审核通过' }, + ], + houseType: [], + houseIndustry: [], + }, + + type: '', + + visibleLog: false, + } + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + columns = [ + { + title: '房屋编码', + dataIndex: 'houseCode', + sorter: true, + width: 300, + render: (text, record) => ( + <> + {`${record.areaName}-${record.roadName}-${record.commName}-${ + record.fullProjName + }-${record.no.toString().padStart(3, '0')}`} +
+ {text} + + ), + }, + { + title: '房屋性质及行业', + dataIndex: 'type', + sorter: true, + width: 150, + render: (text, record) => + this.bindCodeValue(text, 'house_type') + + (text === 2 ? `(${this.bindCodeValue(record.industry, 'house_industry')})` : ''), + }, + { + title: '地址', + dataIndex: 'address', + sorter: true, + }, + { + title: '任务截止时间', + dataIndex: 'endTime', + sorter: true, + width: 150, + }, + { + title: '审核状态', + dataIndex: 'status', + sorter: true, + width: 100, + render: text => this.bindCodeValue(text, 'status'), + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ houseInfo: 'getByTaskId' }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(record.id)}> + {record.state === 3 ? `审核` : `查看`} + + + this.onShowLog(record.id)}>日志 + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData('house_type', 'house_industry').then(codes => { + this.setState({ codes: { ...this.state.codes, ...codes } }, () => { + onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const searchInfo = getSearchInfo({ + query, + queryType: { + type: '=', + industry: '=', + address: 'like', + houseCode: 'like', + status: '=', + }, + }) + + const { data } = await apiAction.page({ + ...params, + searchInfo, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(taskId) { + window.openContentWindow({ + title: '房屋登记', + path: 'business/house/info/form', + param: { + taskId, + table: this.table, + }, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + //#region 自定义方法 + onShowLog(id) { + this.setState({ visibleLog: id }) + } + //#endregion + + render() { + const { codes, type, visibleLog } = this.state + + return ( + +
+ + { + if (values.hasOwnProperty('type')) { + this.setState({ type: values.type }) + } + }} + query={ + + + + 全部 + {codes.houseType.map(item => ( + + {item.value} + + ))} + + + {type == 2 && ( + + + + )} + + + + + + + + + 全部 + {codes.status.map(item => ( + + {item.value} + + ))} + + + + } + /> + + + this.setState({ visibleLog: false })} + destroyOnClose + > + + +
+ ) + } +} diff --git a/Web/src/pages/business/house/task/index.jsx b/Web/src/pages/business/house/task/index.jsx new file mode 100644 index 0000000..95f95ff --- /dev/null +++ b/Web/src/pages/business/house/task/index.jsx @@ -0,0 +1,331 @@ +import React, { Component } from 'react' +import { Card, Checkbox, Drawer, Form, Input, message as Message, Radio, Select, Tag } from 'antd' +import { Auth, Container, HouseLog, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import { getSearchInfo } from 'util/query' +import { checkboxCheckedNone } from 'util/tool' + +/** + * 注释段[\/**\/]为必须要改 + */ + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api.houseTaskPage, +} + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = 'houseTask' + +export default class index extends Component { + state = { + codes: { + houseStatus: [], + houseType: [], + houseIndustry: [], + }, + + type: '', + + visibleLog: false, + } + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + columns = [ + { + title: '房屋编码', + dataIndex: 'houseCode', + sorter: true, + width: 400, + render: (text, record) => ( + <> + {`${record.areaName}-${record.roadName}-${record.commName}-${ + record.fullProjName + }-${record.no.toString().padStart(3, '0')}`} +
+ {text} + + ), + }, + { + title: '房屋性质及行业', + dataIndex: 'type', + sorter: true, + width: 150, + render: (text, record) => + this.bindCodeValue(text, 'house_type') + + (text === 2 ? `(${this.bindCodeValue(record.industry, 'house_industry')})` : ''), + }, + { + title: '地址', + dataIndex: 'address', + sorter: true, + }, + { + title: '任务截止时间', + dataIndex: 'endTime', + sorter: true, + width: 150, + }, + { + title: '建档状态', + dataIndex: 'state', + sorter: true, + width: 100, + render: text => this.bindCodeValue(text, 'house_status'), + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ houseInfo: 'getByTaskId' }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(record.id)}> + {record.state === -1 || record.state === 1 || record.state === 2 + ? `修改` + : record.state === 3 || record.state === 6 + ? `查看` + : `登记`} + + + this.onShowLog(record.id)}>日志 + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData('house_status', 'house_type', 'house_industry').then(codes => { + this.setState({ codes: { ...this.state.codes, ...codes } }, () => { + onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const searchInfo = getSearchInfo({ + query, + queryType: { + type: '=', + industry: '=', + address: 'like', + houseCode: 'like', + state: '=', + }, + }) + + const { data } = await apiAction.page({ + ...params, + searchInfo, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(taskId) { + window.openContentWindow({ + title: '房屋登记', + path: 'business/house/info/form', + param: { + taskId, + table: this.table, + }, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + //#region 自定义方法 + onShowLog(id) { + this.setState({ visibleLog: id }) + } + //#endregion + + render() { + const { codes, type, visibleLog } = this.state + + return ( + +
+ + { + if (values.hasOwnProperty('type')) { + this.setState({ type: values.type }) + } + if (values.hasOwnProperty('state')) { + const value = checkboxCheckedNone({ + value: values.state, + length: codes.houseStatus.length, + required: true, + }) + return { + state: value, + } + } + }} + query={ + + + + 全部 + {codes.houseType.map(item => ( + + {item.value} + + ))} + + + {type == 2 && ( + + + + )} + + + + + + + + + 全部 + {codes.houseStatus.map(item => ( + + {item.value} + + ))} + + + + } + /> + + + this.setState({ visibleLog: false })} + destroyOnClose + > + + +
+ ) + } +} diff --git a/Web/src/pages/business/house/zone/form.jsx b/Web/src/pages/business/house/zone/form.jsx new file mode 100644 index 0000000..b97e82a --- /dev/null +++ b/Web/src/pages/business/house/zone/form.jsx @@ -0,0 +1,194 @@ +import React, { Component } from 'react' +import { Form, Input, InputNumber, Spin, TreeSelect } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep, pickBy } from 'lodash' +import { api } from 'common/api' +import { numberToChinese } from 'util/format' +import store from 'store' + +const { getState, subscribe } = store +const storePath = 'user' +const initialValues = { + sort: 100, +} +export default class form extends Component { + state = { + // 加载状态 + loading: true, + exist: false, + options: { + orgData: [], + }, + user: getState(storePath), + } + + constructor(props) { + super(props) + + this.unsubscribe = subscribe(storePath, () => { + this.setState({ + user: getState(storePath), + }) + }) + } + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + componentWillUnmount() { + this.unsubscribe() + } + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + const { user } = this.state + if (params.id) { + this.setState({ + loading: true, + }) + + api.houseZoneGetById({ zoneId: params.id }).then(({ data }) => { + this.record = data + + this.setState({ + loading: false, + }) + }) + } + // this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + const orgData = await this.loadOrgData() + + this.setState({ + exist: !!params.id, + options: { orgData }, + }) + + //街道角色新增,不管左侧树选中与否,默认值均为本街道 + if (user.adminType === 2) { + user.roles.map(role => { + if (role.code == 'road_manager') { + params.orgId = user.loginEmpInfo.orgId + } + }) + } + + this.record = { + pid: params.orgId, + ...this.record, + } + + //#endregion + if (!params.id && !!params.orgId) { + this.onOrgIdChanged(params.orgId) + } + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false, + }) + } + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格 + //#endregion + return postData + } + } + + async loadOrgData() { + const { data } = await api.getOrgTree({ type: 4 }) + return data + } + + onOrgIdChanged(value) { + this.loading = true + + api.houseZoneAutoIncrement({ roadId: value }) + .then(({ data }) => { + this.form.current.setFieldsValue({ + name: `片区${numberToChinese(data)}`, + sort: data, + }) + }) + .catch(() => { + this.form.current.setFieldsValue({ + name: '', + sort: 0, + }) + }) + .finally(() => { + this.loading = false + }) + } + + render() { + return ( +
+ }> +
+ + this.onOrgIdChanged(value)} + disabled={this.state.exist} + /> + + + + + + + + + + + +
+
+
+ ) + } +} diff --git a/Web/src/pages/business/house/zone/index.jsx b/Web/src/pages/business/house/zone/index.jsx new file mode 100644 index 0000000..bf84083 --- /dev/null +++ b/Web/src/pages/business/house/zone/index.jsx @@ -0,0 +1,277 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, message as Message, Popconfirm } from 'antd' +import { + AntIcon, + Auth, + Container, + ModalForm, + QueryTable, + QueryTableActions, + QueryTreeLayout, +} from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { toCamelCase } from 'util/format' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import FormBody from './form' + +const apiAction = { + tree: api.getOrgTree, + page: api.houseZonePage, + add: api.houseZoneAdd, + edit: api.houseZoneEdit, + delete: api.sysOrgDelete, +} + +const name = '片区' + +export default class index extends Component { + // 树框架实例 + treeLayout = React.createRef() + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + // 树选中节点 + selectId = undefined + + // 表格字段 + columns = [ + { + title: '片区名称', + width: '400px', + dataIndex: 'name', + sorter: true, + }, + { + title: '排序', + width: '80px', + dataIndex: 'sort', + sorter: true, + }, + { + title: '备注', + width: '80px', + dataIndex: 'remark', + sorter: true, + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ houseZone: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(this.editForm, record.id)}>编辑 + + + this.onDelete(record)} + > + 删除 + + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + this.table.current.onLoading() + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + query = { + ...query, + pid: this.selectId, + } + + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 调用树结构数据接口 + * [异步,必要] + * @returns + */ + loadTreeData = async () => { + const { data } = await apiAction.tree({ type: 4 }) + return data + } + + /** + * 树节点选中事件 + * [必要] + * @param {*} id + */ + onSelectTree(id) { + this.selectId = id + this.table.current.onReloadData() + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code === code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(modal, id) { + modal.current.open({ + orgId: this.selectId, + // record, + id, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + this.table.current.onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + this.treeLayout.current.onReloadData() + this.table.current.onReloadData() + } catch { + this.table.current.onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + //#region 自定义方法 + //#endregion + + render() { + return ( + this.onSelectTree(key)} + > + + + + + + + + } + operator={ + + } + /> + + + + this.onAction()} + > + + + + this.onAction()} + > + + + + ) + } +} diff --git a/Web/src/pages/business/inspection/dir/index.jsx b/Web/src/pages/business/inspection/dir/index.jsx new file mode 100644 index 0000000..fd6c200 --- /dev/null +++ b/Web/src/pages/business/inspection/dir/index.jsx @@ -0,0 +1,273 @@ +import React, { Component } from 'react' +import { + Button, + Card, + Checkbox, + Col, + Form, + Input, + Modal, + Row, + Space, + Tooltip, + Typography, +} from 'antd' +import { AntIcon, Auth, Container, ModalForm, QueryList } from 'components' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import { getSearchInfo, QueryType } from 'util/query' +import { api } from 'common/api' +import moment from 'moment' + +import PreviewBody from './preview' + +/** + * 注释段[\/**\/]为必须要改 + */ + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api.inspectionOrgDirPage, + publish: api.inspectionOrgDirPublish, +} + +/** + * 用于弹窗标题 + * [必要] + */ +const name = '' + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = 'inspectionOrgDir' + +export default class index extends Component { + state = { + codes: {}, + } + + // 表格实例 + list = React.createRef() + + preview = React.createRef() + + columns = [] + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.list.current + onLoading() + getDictData(/**/).then(codes => { + this.setState({ codes }, () => { + onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const searchInfo = getSearchInfo({ + query, + queryType: { + no: QueryType.Like, + }, + }) + const { data } = await apiAction.page({ + ...params, + searchInfo, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id) { + modal.current.open({ id }) + } + + //#region 自定义方法 + renderStatus(type, status) { + const STYLE = { + success: { + class: 'success', + label: '已过审', + }, + warning: { + class: 'warning', + label: '待审核', + }, + error: { + class: 'error', + label: '未通过', + }, + } + let style + switch (status) { + case -2: + case 2: + default: + style = STYLE.success + break + case 1: + style = STYLE.warning + break + case -1: + style = STYLE.error + break + } + return
{style.label}
+ } + + renderItem(record) { + const { id, no, orgAmount, removedOrgAmount, newOrgAmount, publishTime } = record + return ( +
+
+ +
+ + 名录发布期号:{no} + +
+
+
+
+ + + + +
+ + 包含机构数量 + {orgAmount} +
+ + +
+ + 被除名机构数量 + {removedOrgAmount} +
+ + +
+ + 同比上一年新入或回归机构数量 + {newOrgAmount} +
+ + +
+ + 发布日期 + + {moment(publishTime).format('YYYY-MM-DD')} + +
+ +
+ + + + +
+
+
+ ) + } + //#endregion + + render() { + return ( + +
+ + + + + + + } + operator={ + + } + renderItem={record => this.renderItem(record)} + /> + + + this.list.current.onReloadData()} + > + + +
+ ) + } +} diff --git a/Web/src/pages/business/inspection/dir/preview.jsx b/Web/src/pages/business/inspection/dir/preview.jsx new file mode 100644 index 0000000..7d6934e --- /dev/null +++ b/Web/src/pages/business/inspection/dir/preview.jsx @@ -0,0 +1,141 @@ +import React, { Component } from 'react' +import { Alert, Col, Empty, Row, Table, Tag } from 'antd' +import { AntIcon, Container } from 'components' +import { api } from 'common/api' + +export default class preview extends Component { + state = { + loading: { + indicator: , + }, + data: [], + stage: null, + count: 0, + } + + columns = [ + { + align: 'center', + width: 80, + render: (text, record) => { + switch (record.status) { + case 1: + return NEW + case 0: + default: + return null + case -1: + return + } + }, + }, + { + dataIndex: 'code', + title: '机构备案编号', + render: (text, record) => { + switch (record.status) { + case 1: + case 0: + default: + return text + case -1: + return {text} + } + }, + }, + { + dataIndex: 'name', + title: '机构名称', + }, + { + dataIndex: 'address', + title: '地址', + }, + ] + + componentDidMount() { + this.props.created && this.props.created(this) + } + + async fillData() { + const state = { + loading: false, + } + const { data } = await api.inspectionOrgDirPreview() + + if (data) { + const { stage, count, list } = data + Object.assign(state, { + data: list, + stage, + count, + }) + } + + this.setState(state) + } + + async getData() { + return null + } + + render() { + const { loading, data, stage, count, publishing } = this.state + + if (stage) { + return ( + <> +
+ + +
+ 新名录发布期号{stage.no} + ,机构合计 + {count}个 +
+ +
+
+
+ record.id} + /> +
+ + + + + 红牌将除名机构在名录发布时会删除 + + + NEW + 今年新增或回归机构 + + + +
+ + ) + } else { + return ( + <> +
+ +
+ + ) + } + } +} diff --git a/Web/src/pages/business/inspection/dir/score.jsx b/Web/src/pages/business/inspection/dir/score.jsx new file mode 100644 index 0000000..251ff9b --- /dev/null +++ b/Web/src/pages/business/inspection/dir/score.jsx @@ -0,0 +1,381 @@ +import React, { Component } from 'react' +import { + Alert, + Button, + Card, + Form, + Input, + InputNumber, + message as Message, + Popconfirm, + Space, + Tag, + Typography, +} from 'antd' +import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' + +/** + * 注释段[\/**\/]为必须要改 + */ + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api.inspectionOrgDirListScore, +} + +/** + * 用于弹窗标题 + * [必要] + */ +const name = '' + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = 'inspectionOrgDir' + +export default class score extends Component { + state = { + codes: {}, + + disabled: true, + saving: false, + } + + // 表格实例 + table = React.createRef() + + stageForm = React.createRef() + + tableForm = React.createRef() + + columns = [ + { + dataIndex: 'code', + title: '机构备案编号', + width: 120, + }, + { + dataIndex: 'name', + title: '鉴定机构名称', + width: 300, + render: (text, record) => ( + + {text} + {!!record.isNew && NEW} + + ), + }, + { + dataIndex: 'prevScoreResult', + title: '往期名录信用评价', + width: 120, + render: text => + this.bindCodeValue(text || 0, 'inspection_dir_result', ({ value, extCode }) => { + return {value} + }), + }, + { + dataIndex: 'score', + title: '本期名录信用评分', + width: 180, + render: (text, record, index) => ( + + + + ), + }, + { + dataIndex: 'scoreResult', + title: '本期名录信用评价结果', + width: 120, + render: (text, record, index) => ( + <> + + {this.bindCodeValue( + text || 0, + 'inspection_dir_result', + ({ value, extCode }) => { + return {value} + } + )} + + + ), + }, + ] + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + async componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + + const { data } = await api.inspectionOrgDirDetail() + if (data) { + this.stageForm.current.setFieldsValue(data) + } + + getDictData('inspection_dir_result').then(codes => { + this.setState({ codes }, () => { + onLoadData() + + this.setState({ + disabled: false, + }) + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const { data } = await apiAction.page({ + ...params, + ...query, + }) + + const values = {} + data.forEach((item, index) => { + values[index] = item + }) + + this.tableForm.current.setFieldsValue(values) + + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name, formatter) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + if (formatter) { + return formatter(c) + } else { + return c.value + } + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id) { + modal.current.open({ id }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + /** + * 删除 + * @param {*} id + */ + onDelete(id) { + this.onAction(apiAction.delete({ id }), '删除成功') + } + + //#region 自定义方法 + onChangeScore(key, changedValues, record) { + const score = changedValues[key].score, + codes = this.state.codes.inspectionDirResult + let scoreResult = 0 + for (const code of codes) { + if (code.extCode) { + const { range } = code.extCode + if (score >= range[0] && score < (range[1] || Infinity)) { + scoreResult = code.code + break + } + } + } + + /** + * 特殊逻辑处理 + * 如果往期和本期皆为黄牌,直接设定本期为红牌 + */ + if (record.prevScoreResult == 3 && scoreResult == 3) { + scoreResult = 4 + } + this.table.current.onChangeData(key, { scoreResult }) + } + + async onFinish(values) { + const detail = [], + scores = this.tableForm.current.getFieldsValue() + + for (const key in scores) { + const item = scores[key] + if (item.score !== null) { + item.orgId = item.id + detail.push(item) + } + } + + this.setState({ saving: true }) + try { + await api.inspectionOrgDirSaveScore({ + ...values, + detail, + }) + + Message.success('保存评价成功') + } finally { + this.setState({ saving: false }) + } + } + //#endregion + + render() { + const { disabled, saving } = this.state + + return ( + +
+ + 考核标准参考 +
    +
  • 考核结果评分10分的,信用评级为优秀
  • +
  • 评分达到8分以上(含8分)、10分以下的,信用评级为合格
  • +
  • + 评分为6分以上(含6分)、8分以下的,挂黄牌警告, + + 连续两年挂黄牌警告的,将视作红牌 + +
  • +
  • + 评分为6分以下的, + 挂红牌,将移除鉴定机构名录 +
  • +
+ + } + /> +
+ + } + editable + form={this.tableForm} + formProps={{ + onValuesChange: changedValues => { + Object.keys(changedValues).forEach(key => { + this.onChangeScore( + key, + changedValues, + this.table.current.getRecord(key) + ) + }) + }, + }} + operator={ + +
this.onFinish(values)} + > + + + + + + + + + + +
+ } + /> +
+
+ ) + } +} diff --git a/Web/src/pages/business/inspection/register/detail.jsx b/Web/src/pages/business/inspection/register/detail.jsx new file mode 100644 index 0000000..8ef73c7 --- /dev/null +++ b/Web/src/pages/business/inspection/register/detail.jsx @@ -0,0 +1,314 @@ +import React, { Component } from 'react' +import { + Button, + Card, + Col, + Divider, + Form, + Input, + message as Message, + Modal, + Row, + Space, + Spin, + Tag, + Typography, + Upload, +} from 'antd' +import { AntIcon, Auth, Container } from 'components' +import { api } from 'common/api' +import { BlobToBase64, GetFileName, PreviewFile } from 'util/file' +import { isEqual } from 'lodash' + +export default class detail extends Component { + state = { + loading: false, + saving: false, + record: {}, + updateRecord: {}, + } + + form = React.createRef() + + componentDidMount() { + this.fillData() + } + + async fillData() { + this.setState({ loading: true }) + const { data, uploads, action } = this.props + const keys = uploads.map(p => p.key) + let record = {}, + updateRecord = {} + if (action === 'review') { + record = data.updateRecord + } else if (action === 'updateReview') { + record = data.record + updateRecord = data.updateRecord + } else { + record = data.record || data.updateRecord + } + + for (const key of keys) { + const fileValue = [] + const item = record[key] + if (!Array.isArray(item)) { + const fileList = !item || !item.length ? [] : item.split(',') + for (const fileId of fileList) { + try { + const file = await PreviewFile(fileId) + const base64 = await BlobToBase64(file) + fileValue.push({ + uid: fileId, + response: fileId, + name: file.name, + url: base64, + status: 'done', + }) + } catch { + const { data: file } = await api.sysFileInfoDetail({ id: fileId }) + fileValue.push({ + uid: fileId, + response: '文件已丢失', + name: file.fileOriginName, + status: 'error', + }) + } + } + } + + record[key] = fileValue + + const updateFileValue = [] + const updateItem = updateRecord[key] + if (!Array.isArray(updateItem)) { + const fileList = !updateItem || !updateItem.length ? [] : updateItem.split(',') + for (const fileId of fileList) { + try { + const file = await PreviewFile(fileId) + const base64 = await BlobToBase64(file) + updateFileValue.push({ + uid: fileId, + response: fileId, + name: file.name, + url: base64, + status: 'done', + }) + } catch { + const { data: file } = await api.sysFileInfoDetail({ id: fileId }) + updateFileValue.push({ + uid: fileId, + response: '文件已丢失', + name: file.fileOriginName, + status: 'error', + }) + } + } + } + + updateRecord[key] = updateFileValue + } + + this.setState({ + loading: false, + record, + updateRecord, + }) + } + + onFinish(approved = false) { + const remark = this.form.current.getFieldValue('remark') + if (!approved && !remark) { + Message.error('请输入退回理由') + return + } + + Modal.confirm({ + title: '提示', + content: '是否确定' + ['退回', '审核通过'][+approved], + onOk: async () => { + this.setState({ saving: true }) + try { + const { id } = this.props.data + if (approved) { + await api.inspectionOrgReviewApproved({ id }) + } else { + await api.inspectionOrgReviewNotApproved({ id, remark }) + } + + // 提交成功时应该关闭当前页并且等待 + Modal.info({ + title: '提示', + content: '审核已完成', + onOk: () => { + if (this.props.list) { + this.props.list.current.onReloadData() + } + window.closeContentWindow() + }, + }) + } finally { + this.setState({ saving: false }) + } + }, + }) + } + + async onFileDownload(file) { + const { data, headers } = await api.sysFileInfoDownload({ id: file.response }) + const url = window.URL.createObjectURL(data) + const fileName = GetFileName(headers['content-disposition']) + const a = document.createElement('a') + a.href = url + a.download = fileName + a.click() + window.URL.revokeObjectURL(url) + a.remove() + } + + compareTo(scoure, target, type) { + if (type === 'file') { + const s = scoure.map(p => p.uid), + t = target.map(p => p.uid) + + return isEqual(s.sort(), t.sort()) + } else { + return scoure === target + } + } + + renderDetail(record, compare) { + const { loading } = this.state + + const { uploads } = this.props + + return ( + <> + 详细信息 + +
+
申请材料
+ }> + } direction="vertical"> + {uploads.map((item, i) => { + let fileList = [] + if ( + record && + record[item.key] && + Array.isArray(record[item.key]) + ) { + fileList = record[item.key] + } + + let compareResult = true + if (compare) { + compareResult = this.compareTo( + fileList, + compare[item.key] || [], + 'file' + ) + } + + return ( +
+ +
+ {item.label} + false} + onDownload={file => + this.onFileDownload(file) + } + fileList={fileList} + /> + + {compare && !loading && ( + + {compareResult ? ( + 未修改 + ) : ( + 已修改 + )} + + )} + + + ) + })} + + + + + + ) + } + + render() { + const { loading, saving, record, updateRecord } = this.state + + const { uploads, action } = this.props + + return ( + +
+ + {action === 'updateReview' ? ( + <> +
+ 修改前 + {this.renderDetail(record)} + + + 修改后 + {this.renderDetail(updateRecord, record)} + + + ) : ( + {this.renderDetail(record)} + )} + + 历史记录 + + {['review', 'updateReview'].includes(action) && ( +
+ +
+
退回理由
+ + + +
+ + + + +
+
+
+ + )} +
+ + ) + } +} diff --git a/Web/src/pages/business/inspection/register/form.jsx b/Web/src/pages/business/inspection/register/form.jsx new file mode 100644 index 0000000..6018e7f --- /dev/null +++ b/Web/src/pages/business/inspection/register/form.jsx @@ -0,0 +1,329 @@ +import React, { Component } from 'react' +import { + Alert, + Button, + Card, + Col, + Divider, + Form, + Input, + Modal, + Row, + Space, + Spin, + Upload, +} from 'antd' +import { AntIcon, Container } from 'components' +import { api } from 'common/api' +import moment from 'moment' +import { BlobToBase64, GetFileName, PreviewFile } from 'util/file' + +const layout = { + labelCol: { flex: '140px' }, + wrapperCol: { flex: '1' }, +} + +export default class form extends Component { + state = { + loading: false, + saving: false, + code: '', + date: '', + record: {}, + } + + form = React.createRef() + + componentDidMount() { + this.fillData() + } + + async fillData() { + this.setState({ loading: true }) + const { data, uploads } = this.props + const record = data && (data.updateRecord || data.record) + if (record) { + const keys = uploads.map(p => p.key) + for (const key of keys) { + const fileValue = [] + const item = record[key] + if (!Array.isArray(item)) { + const fileList = !item || !item.length ? [] : item.split(',') + for (const fileId of fileList) { + try { + const file = await PreviewFile(fileId) + const base64 = await BlobToBase64(file) + fileValue.push({ + uid: fileId, + response: fileId, + name: file.name, + url: base64, + status: 'done', + }) + } catch { + const { data: file } = await api.sysFileInfoDetail({ id: fileId }) + fileValue.push({ + uid: fileId, + response: '文件已丢失', + name: file.fileOriginName, + status: 'error', + }) + } + } + } + + record[key] = fileValue + } + + this.form.current.setFieldsValue(record) + + this.setState({ + loading: false, + code: data.code, + date: moment(data.createdTime).format('YYYY年MM月DD日'), + record, + }) + } else { + const { data: code } = await api.inspectionOrgNewCode() + this.setState({ + loading: false, + code, + date: moment().format('YYYY年MM月DD日'), + }) + } + } + + async onFinish(values) { + this.setState({ saving: true }) + const { uploads, action } = this.props + try { + for (const upload of uploads) { + values[upload.key] = (values[upload.key] || []) + .map(item => (item.uid.startsWith('rc-upload') ? item.response : item.uid)) + .join(',') + } + if (action === 'edit') { + await api.inspectionOrgUpdate(values) + } else { + await api.inspectionOrgRegister(values) + } + + // 提交成功时应该关闭当前页并且等待 + Modal.info({ + title: '提示', + content: '备案已提交申请,请等待上级部门审核', + onOk() { + window.closeContentWindow() + }, + }) + } finally { + this.setState({ saving: false }) + } + } + + async onFileUpload({ file, onProgress, onSuccess, onError }) { + onProgress({ + percent: 0, + }) + const fd = new FormData() + fd.append('file', file) + try { + const { data: fileId } = await api.sysFileInfoUpload(fd) + onSuccess(fileId) + } catch { + onError() + } + } + + async onFileDownload(file) { + const { data, headers } = await api.sysFileInfoDownload({ id: file.response }) + const url = window.URL.createObjectURL(data) + const fileName = GetFileName(headers['content-disposition']) + const a = document.createElement('a') + a.href = url + a.download = fileName + a.click() + window.URL.revokeObjectURL(url) + a.remove() + } + + render() { + const { loading, saving, code, date, record } = this.state + + const { uploads, action } = this.props + + return ( + +
+
this.onFinish(values)}> + {record.updateStatus === -1 && ( + <> + +
+ + )} + +
+
基本信息
+ }> + +
+ + {code} + + + + + {date} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
申请材料
+ }> + } direction="vertical"> + {uploads.map((item, i) => ( +
+ { + if (Array.isArray(e)) { + return e + } + return e && e.fileList + }} + rules={[ + { + required: item.required, + message: `请上传${item.label}`, + }, + ]} + > + this.onFileUpload(e)} + showUploadList={{ + showRemoveIcon: true, + showDownloadIcon: true, + }} + onPreview={() => false} + onDownload={file => this.onFileDownload(file)} + > + + + +
+ ))} +
+
+
+
+ +
+
+ + + + ) + } +} diff --git a/Web/src/pages/business/inspection/register/index.jsx b/Web/src/pages/business/inspection/register/index.jsx new file mode 100644 index 0000000..6c6b3d8 --- /dev/null +++ b/Web/src/pages/business/inspection/register/index.jsx @@ -0,0 +1,94 @@ +import React, { Component } from 'react' + +import { api } from 'common/api' + +import FormBody from './form' +import DetailBody from './detail' +import { Spin } from 'antd' +import { AntIcon } from 'components' + +const uploads = [ + { + key: 'applicationFormFiles', + label: '名录申请表', + required: true, + }, + { + key: 'orgBusinessLicenseFiles', + label: '机构营业执照和法人证书', + required: true, + }, + { + key: 'inspectionQualificationCertificateFiles', + label: '建设工程质量检测资质证书或检验检测机构资质认定证书', + required: true, + }, + { + key: 'officeInformationFiles', + label: '办公场所资料(房产证、不动产登记证或房屋租赁合同等)', + required: true, + }, + { + key: 'employeeCertificateFiles', + label: '符合条件的从业人员职称证明、学历证明、劳动合同、社保证明等', + required: true, + }, + { + key: 'calibrationCertificateFiles', + label: '开展房屋安全鉴定工作必要的设备计量检定证书、校准证书', + required: true, + }, + { + key: 'otherFiles', + label: '其他资料', + }, +] + +export default class index extends Component { + state = { + loading: true, + data: {}, + } + + componentDidMount() { + this.fillData() + } + + async fillData() { + this.setState({ loading: true }) + const id = this.props.param && this.props.param.id + const { data } = await api.inspectionOrgDetail({ id }) + this.setState({ + loading: false, + data, + }) + } + + render() { + const { loading, data } = this.state + + const { param } = this.props + + const editable = + !data || + (!data.record && !data.updateRecord) || // 没有记录时需登记 + ([0, 1].includes(data.status) && data.updateStatus === -1) || // 登记中或已登记时,被退回的状态 + (param && ['register', 'edit', 'reRegistration'].includes(param.action)) // 参数标识可修改 + + return ( + } style={{ height: 400 }}> + {!loading && + (editable ? ( + + ) : ( + + ))} + + ) + } +} diff --git a/Web/src/pages/business/inspection/review/history.jsx b/Web/src/pages/business/inspection/review/history.jsx new file mode 100644 index 0000000..218beac --- /dev/null +++ b/Web/src/pages/business/inspection/review/history.jsx @@ -0,0 +1,122 @@ +import React, { Component } from 'react' +import { Form, Space, Spin, Table, Tabs, Typography } from 'antd' +import { AntIcon, Container } from 'components' +import { api } from 'common/api' + +const columns = [ + { + dataIndex: 'type', + title: '备案类型', + width: 120, + render: text => (text === 1 ? '登记备案' : '修改备案'), + }, + { + dataIndex: 'createdTime', + title: '提交时间', + width: 200, + }, + { + dataIndex: 'updatedTime', + title: '审核时间', + width: 200, + }, + { + dataIndex: 'status', + title: '审核结果', + width: 200, + render: text => + text === 2 ? ( + + 通过 + + ) : ( + + 未通过 + + ), + }, + { + dataIndex: 'remark', + title: '未通过理由', + }, +] + +export default class history extends Component { + state = { + // 加载状态 + loading: { + indicator: , + }, + code: '', + data: [], + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + const state = { loading: false } + //#region 从后端转换成前段所需格式,也可以在此处调用获取详细数据接口 + if (params.id) { + const { data } = await api.inspectionOrgHistory({ id: params.id }) + state.data = data + + state.code = params.code + } + //#endregion + + this.setState(state) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() {} + + //#region 自定义方法 + //#endregion + + render() { + const { loading, data, code } = this.state + + const props = { + loading, + pagination: false, + columns, + dataSource: data, + bordered: true, + size: 'middle', + rowKey: record => record.id, + } + + return ( + <> +
+ + 机构备案编号:{code} + +
+
+ + ) + } +} diff --git a/Web/src/pages/business/inspection/review/index.jsx b/Web/src/pages/business/inspection/review/index.jsx new file mode 100644 index 0000000..2517b9c --- /dev/null +++ b/Web/src/pages/business/inspection/review/index.jsx @@ -0,0 +1,14 @@ +import React, { Component } from 'react' +import { api } from 'common/api' + +import ListBody from './list' + +const apiAction = { + page: api.inspectionOrgReviewPage, +} + +export default class index extends Component { + render() { + return + } +} diff --git a/Web/src/pages/business/inspection/review/list.jsx b/Web/src/pages/business/inspection/review/list.jsx new file mode 100644 index 0000000..6450820 --- /dev/null +++ b/Web/src/pages/business/inspection/review/list.jsx @@ -0,0 +1,305 @@ +import React, { Component } from 'react' +import { Button, Card, Checkbox, Col, Form, Input, Row, Space, Tooltip, Typography } from 'antd' +import { AntIcon, Auth, Container, ModalForm, QueryList } from 'components' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import { getSearchInfo, QueryType } from 'util/query' + +import HistoryBody from './history' + +const { Link } = Typography + +/** + * 注释段[\/**\/]为必须要改 + */ + +/** + * 用于弹窗标题 + * [必要] + */ +const name = '' + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = 'inspectionOrg' + +export default class list extends Component { + state = { + codes: {}, + } + + // 表格实例 + list = React.createRef() + + history = React.createRef() + + columns = [] + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.list.current + onLoading() + getDictData(/**/).then(codes => { + this.setState({ codes }, () => { + onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const searchInfo = getSearchInfo({ + query, + queryType: { + code: QueryType.Like, + updateStatus: QueryType.Equal, + }, + }) + const { data } = await this.props.api({ + ...params, + searchInfo, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id, code) { + modal.current.open({ id, code }) + } + + //#region 自定义方法 + renderStatus(type, status) { + const STYLE = { + success: { + class: 'success', + label: '已过审', + }, + warning: { + class: 'warning', + label: '待审核', + }, + error: { + class: 'error', + label: '未通过', + }, + } + let style + switch (status) { + case -2: + case 2: + default: + style = STYLE.success + break + case 1: + style = STYLE.warning + break + case -1: + style = STYLE.error + break + } + return
{style.label}
+ } + + renderItem(record) { + const { + id, + code, + name, + creditCode, + address, + contacts, + contactsPhone, + legalPerson, + orgTelephone, + status, + updateType, + updateStatus, + updateCount, + updatedTime, + } = record + return ( +
+
+ +
+ + 机构备案编号:{code} + {name} + +
+ {this.renderStatus(updateType, updateStatus)} +
+
+
+ +
+ + +
+ + 备案资料更新日期 + {updatedTime} +
+ + +
+ + 机构地址 + + {address} + +
+ + +
+ + 联系人手机号 + {contactsPhone} +
+ + +
+ + 历史审核记录 + this.onOpen(this.history, id, code)}> + 查看记录({updateCount}条) + +
+ + + + + + {updateStatus === 1 && ( + + )} + + {[-2, 2].includes(updateStatus) && ( + + )} + + + + + ) + } + //#endregion + + render() { + return ( + +
+ + + + + + + + 未通过 + 待审核 + 已过审 + + + + } + renderItem={record => this.renderItem(record)} + /> + + + + + +
+ ) + } +} diff --git a/Web/src/pages/business/inspection/review/update.jsx b/Web/src/pages/business/inspection/review/update.jsx new file mode 100644 index 0000000..2baefc5 --- /dev/null +++ b/Web/src/pages/business/inspection/review/update.jsx @@ -0,0 +1,14 @@ +import React, { Component } from 'react' +import { api } from 'common/api' + +import ListBody from './list' + +const apiAction = { + page: api.inspectionOrgReviewUpdatePage, +} + +export default class update extends Component { + render() { + return + } +} diff --git a/Web/src/pages/business/statistics/summary/index.jsx b/Web/src/pages/business/statistics/summary/index.jsx new file mode 100644 index 0000000..37fba3b --- /dev/null +++ b/Web/src/pages/business/statistics/summary/index.jsx @@ -0,0 +1,88 @@ +import React, { Component } from 'react' +import { Radio, Tabs } from 'antd' +import { ComponentDynamic } from 'components' +import AntIcon from 'components/ant-icon' + +const tabs = [ + { + title: '按房屋等级', + component: () => import('./tab1'), + }, + { + title: '按房屋结构', + component: () => import('./tab2'), + }, +] + +export default class index extends Component { + + state = { + activeKey: '0', + types: ['charts', 'charts'] + } + + render() { + + const { activeKey, types } = this.state + + return ( +
+
+
+ { + const t = [...types] + t[activeKey] = e.target.value + this.setState({ + types: t + }) + }} + > + + + + + + + + } + onChange={(activeKey) => this.setState({ activeKey })} + > + { + tabs.map((item, i) => ( + + )) + } + +
+ { + tabs.map((item, i) => ( +
+ +
+ )) + } +
+
+
+
+ ) + } +} diff --git a/Web/src/pages/business/statistics/summary/tab1/charts.jsx b/Web/src/pages/business/statistics/summary/tab1/charts.jsx new file mode 100644 index 0000000..c6be21d --- /dev/null +++ b/Web/src/pages/business/statistics/summary/tab1/charts.jsx @@ -0,0 +1,202 @@ +import React, { Component } from 'react' +import { Card, Col, Row } from 'antd' +import * as echarts from 'echarts' + +const echartsColors = [ + { from: '#14dbff', to: '#007dff' }, + { from: '#45f4a6', to: '#3bb27d' }, + { from: '#fbb456', to: '#f1961b' }, + { from: '#fa7148', to: '#ef5932' }, +] + +function itemColor(index) { + return { + type: 'linear', + x: 0, + y: 0, + x2: 1, + y2: 1, + colorStops: [ + { + offset: 0, + color: echartsColors[index % echartsColors.length].from, + }, + { + offset: 1, + color: echartsColors[index % echartsColors.length].to, + }, + ], + } +} + +function initChart1(dom) { + const chart = echarts.init(dom); + chart.setOption({ + legend: { + top: 'bottom', + }, + tooltip: { + formatter: '{b} : {c}幢 ({d}%)', + }, + toolbox: { + show: true, + feature: { + mark: { show: true }, + restore: { show: true }, + saveAsImage: { show: true }, + }, + }, + series: [ + { + name: '面积模式', + type: 'pie', + radius: [50, 150], + startAngle: 110, + center: ['50%', '50%'], + roseType: 'area', + label: { + formatter: '{d}', + }, + data: [ + { + value: 70, + name: 'A级', + itemStyle: { + color: itemColor(0), + }, + }, + { + value: 38, + name: 'B级', + itemStyle: { + color: itemColor(1), + }, + }, + { + value: 32, + name: 'C级', + itemStyle: { + color: itemColor(2), + }, + }, + { + value: 30, + name: 'D级', + itemStyle: { + color: itemColor(3), + }, + }, + ], + }, + ], + }); +} + +function initChart2(dom) { + const chart = echarts.init(dom); + chart.setOption({ + tooltip: { + formatter: '{b} : {c}幢', + }, + toolbox: { + show: true, + feature: { + mark: { show: true }, + restore: { show: true }, + saveAsImage: { show: true }, + }, + }, + xAxis: { + type: 'category', + data: ['A级', 'B级', 'C级', 'D级'], + }, + yAxis: { + type: 'value', + }, + series: [ + { + barWidth: 20, + itemStyle: { + color: itemColor(0), + borderRadius: 10, + }, + data: [120, 200, 150, 80], + type: 'bar', + }, + ], + }); +} + +function initChart3(dom) { + const chart = echarts.init(dom); + chart.setOption({ + tooltip: { + formatter: '{b} : {c}幢', + }, + toolbox: { + show: true, + feature: { + mark: { show: true }, + restore: { show: true }, + saveAsImage: { show: true }, + }, + }, + xAxis: { + type: 'category', + data: ['A级', 'B级', 'C级', 'D级'], + }, + yAxis: { + type: 'value', + }, + series: [ + { + data: [820, 932, 800, 900], + showSymbol: false, + lineStyle: { + width: 5, + shadowBlur: 5, + shadowOffsetY: 3, + shadowColor: echartsColors[0].from, + opacity: 0.5, + cap: 'round', + }, + itemStyle: { + color: itemColor(0), + }, + type: 'line', + smooth: true, + }, + ], + }); +} + +export default class charts extends Component { + + componentDidMount() { + initChart1(this.refs['chart-1']) + initChart2(this.refs['chart-2']) + initChart3(this.refs['chart-3']) + } + + render() { + return ( + <> + +
+ +
+
+ + + +
+
+ + + +
+
+ + ) + } +} diff --git a/Web/src/pages/business/statistics/summary/tab1/index.jsx b/Web/src/pages/business/statistics/summary/tab1/index.jsx new file mode 100644 index 0000000..b8329fe --- /dev/null +++ b/Web/src/pages/business/statistics/summary/tab1/index.jsx @@ -0,0 +1,68 @@ +import React, { Component } from 'react' +import { Button, Card, Cascader, Divider, Form, Radio } from 'antd' +import { Container } from 'components' + +import StatisticsCharts from './charts' +import StatisticsTable from './table' + +export default class index extends Component { + + state = { + render: 'charts' + } + + static getDerivedStateFromProps(props) { + return { + render: props.type + } + } + + render() { + return ( + + +
+ + + 全部 + 住宅 + 非住宅 + + + + + + 全部 + 待建档 + 暂存 + 待提交 + 退回 + 待审核 + 审核通过 + + + + + + 全部 + 国有土地 + 集体土地 + + + + + + + +
+ + +
+ +
+ {this.state.render == 'charts' && } + {this.state.render == 'table' && } +
+ ) + } +} diff --git a/Web/src/pages/business/statistics/summary/tab1/table.jsx b/Web/src/pages/business/statistics/summary/tab1/table.jsx new file mode 100644 index 0000000..6c5bd7e --- /dev/null +++ b/Web/src/pages/business/statistics/summary/tab1/table.jsx @@ -0,0 +1,209 @@ +import React, { Component } from 'react' +import { Card, Table } from 'antd' +import { isEqual } from 'lodash' + +const columns = [ + { + title: '区域', + dataIndex: 'area', + width: 150, + fixed: true + }, + { + title: '总数', + children: [ + { + title: '幢数(占比)', + dataIndex: 'z', + width: 120, + }, + { + title: '建筑面积(占比)', + dataIndex: 'j', + width: 120, + }, + { + title: '户数', + dataIndex: 'h', + width: 80, + }, + ], + }, + { + title: '房屋等级', + children: [ + { + title: '一级', + children: [ + { + title: '幢数', + dataIndex: 'z1', + width: 80, + }, + { + title: '建筑面积', + dataIndex: 'j1', + width: 80, + }, + { + title: '户数', + dataIndex: 'h1', + width: 80, + }, + ], + }, + { + title: '二级', + children: [ + { + title: '幢数', + dataIndex: 'z2', + width: 80, + }, + { + title: '建筑面积', + dataIndex: 'j2', + width: 80, + }, + { + title: '户数', + dataIndex: 'h2', + width: 80, + }, + ], + }, + { + title: '三级', + children: [ + { + title: '幢数', + dataIndex: 'z3', + width: 80, + }, + { + title: '建筑面积', + dataIndex: 'j3', + width: 80, + }, + { + title: '户数', + dataIndex: 'h3', + width: 80, + }, + ], + }, + { + title: '四级', + children: [ + { + title: '幢数', + dataIndex: 'z4', + width: 80, + }, + { + title: '建筑面积', + dataIndex: 'j4', + width: 80, + }, + { + title: '户数', + dataIndex: 'h4', + width: 80, + }, + ], + }, + { + title: 'C级', + children: [ + { + title: '幢数', + dataIndex: 'zc', + width: 80, + }, + { + title: '建筑面积', + dataIndex: 'jc', + width: 80, + }, + { + title: '户数', + dataIndex: 'hc', + width: 80, + }, + ], + }, + { + title: 'D级', + children: [ + { + title: '幢数', + dataIndex: 'zd', + width: 80, + }, + { + title: '建筑面积', + dataIndex: 'jd', + width: 80, + }, + { + title: '户数', + dataIndex: 'hd', + width: 80, + }, + ], + }, + ], + }, +] + +const data = [] +for (let i = 0; i < 30; i++) { + data.push({ + key: i, + area: 'John Brown', + z: 100, + j: 1222.33, + h: 39, + z1: 20, + j1: 20, + h1: 20, + z2: 20, + j2: 20, + h2: 20, + z3: 20, + j3: 20, + h3: 20, + z4: 20, + j4: 20, + h4: 20, + zc: 20, + jc: 20, + hc: 20, + zd: 20, + jd: 20, + hd: 20, + }) +} + +export default class table extends Component { + + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + render() { + return ( + +
+ + ) + } +} diff --git a/Web/src/pages/business/statistics/summary/tab2/charts.jsx b/Web/src/pages/business/statistics/summary/tab2/charts.jsx new file mode 100644 index 0000000..e69de29 diff --git a/Web/src/pages/business/statistics/summary/tab2/index.jsx b/Web/src/pages/business/statistics/summary/tab2/index.jsx new file mode 100644 index 0000000..70b0147 --- /dev/null +++ b/Web/src/pages/business/statistics/summary/tab2/index.jsx @@ -0,0 +1,11 @@ +import React, { Component } from 'react' + +export default class index extends Component { + render() { + return ( +
+ 1 +
+ ) + } +} diff --git a/Web/src/pages/business/statistics/summary/tab2/table.jsx b/Web/src/pages/business/statistics/summary/tab2/table.jsx new file mode 100644 index 0000000..e69de29 diff --git a/Web/src/pages/home/business/inspection.jsx b/Web/src/pages/home/business/inspection.jsx new file mode 100644 index 0000000..2f6de36 --- /dev/null +++ b/Web/src/pages/home/business/inspection.jsx @@ -0,0 +1,144 @@ +import React, { Component } from 'react' +import { Card, Space, Typography } from 'antd' +import { api } from 'common/api' +import moment from 'moment' + +const { Title, Text, Link } = Typography + +export default class inspection extends Component { + state = { + status: {}, + } + + async componentDidMount() { + const { data: status } = await api.inspectionOrgRegisterStatus() + this.setState({ status }) + } + + render() { + const { status } = this.state + + const { beginDate, endDate, inspectionOrg } = status + + if (inspectionOrg === null) + // 未登记 + return ( + + + + 您的机构资质尚未于市住建管理部门备案 + + + 提交备案资料时间:{moment(beginDate).format('MM月DD日')}至 + {moment(endDate).format('MM月DD日')}, + + window.openContentWindowByMenuName('inspection_org_register') + } + > + 前往登记 + + + + + ) + else if (inspectionOrg) { + if (inspectionOrg.status === 0) { + if (inspectionOrg.updateStatus === 1) { + // 登记审核中 + return ( + + + 备案登记审核中… + + + ) + } else if (inspectionOrg.updateStatus === -1) { + // 登记退回 + return ( + + + + 备案登记未通过 + + 未通过理由:{inspectionOrg.remark} + + + window.openContentWindowByMenuName( + 'inspection_org_register' + ) + } + > + 前往登记 + + + + + ) + } + } else if (inspectionOrg.status === 1) { + if (inspectionOrg.updateStatus === 2 || inspectionOrg.updateStatus === -2) { + // 已登记, 可修改 + return ( + + + 您的机构资质已通过了市住建管理部门审核 + + + 如有信息变更,您可以 + + window.openContentWindowByMenuName( + 'inspection_org_register', + { action: 'edit' } + ) + } + > + 修改备案信息 + + + + ) + } else if (inspectionOrg.updateStatus === 1) { + // 修改审核中 + return ( + + + 修改备案信息审核中… + + + ) + } else if (inspectionOrg.updateStatus === -1) { + // 修改审核退回 + return ( + + + + 修改备案信息未通过 + + 未通过理由:{inspectionOrg.remark} + + 您可以 + + window.openContentWindowByMenuName( + 'inspection_org_register', + { action: 'edit' } + ) + } + > + 继续修改 + + 或放弃修改 + + + + ) + } + } + } + + return <> + } +} diff --git a/Web/src/pages/home/charts.jsx b/Web/src/pages/home/charts.jsx new file mode 100644 index 0000000..8f63b22 --- /dev/null +++ b/Web/src/pages/home/charts.jsx @@ -0,0 +1,115 @@ +import React, { Component } from 'react' +import { Card, Dropdown, Form, Menu } from 'antd' +import { AntIcon } from 'components' +import * as echarts from 'echarts' + +const options = { + tooltip: { + trigger: 'axis', + }, + legend: { + show: false, + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + containLabel: true, + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], + }, + yAxis: { + type: 'value', + }, + series: [ + { + name: '邮件营销', + type: 'line', + stack: '总量', + data: [120, 132, 101, 134, 90, 230, 210], + }, + { + name: '联盟广告', + type: 'line', + stack: '总量', + data: [220, 182, 191, 234, 290, 330, 310], + }, + { + name: '视频广告', + type: 'line', + stack: '总量', + data: [150, 232, 201, 154, 190, 330, 410], + }, + { + name: '直接访问', + type: 'line', + stack: '总量', + data: [320, 332, 301, 334, 390, 330, 320], + }, + { + name: '搜索引擎', + type: 'line', + stack: '总量', + data: [820, 932, 901, 934, 1290, 1330, 1320], + }, + ] +} + +export default class charts extends Component { + + componentDidMount() { + const chartDom = this.refs.chart + const myChart = echarts.init(chartDom) + myChart.setOption(options) + window.addEventListener('resize', () => { + myChart.resize() + }) + } + + render() { + return ( + +
+ + + 宁波市 + 鄞州区 + + } + > + + 宁波市 + + + + + + + 2021 + 2020 + 2019 + + } + > + + 2021 + + + + + +
+ + ) + } +} diff --git a/Web/src/pages/home/index.jsx b/Web/src/pages/home/index.jsx new file mode 100644 index 0000000..f783ac5 --- /dev/null +++ b/Web/src/pages/home/index.jsx @@ -0,0 +1,109 @@ +import React, { Component } from 'react' +import { Row, Col, Divider } from 'antd' +import { isEqual } from 'lodash' +import store from 'store' +import { Container, Image, AntIcon, Auth } from 'components' +import moment from 'moment' + +import Statistics from './statistics' +import Task from './task' +import List from './list' +import Notice from './notice' +import Charts from './charts' + +import Inspection from './business/inspection' + +const { getState, subscribe } = store + +const storePath = 'user' + +export default class index extends Component { + state = { + [storePath]: getState(storePath), + } + + constructor(props) { + super(props) + + this.unsubscribe = subscribe(storePath, () => { + this.setState(getState(storePath)) + }) + } + + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + componentWillUnmount() { + this.unsubscribe() + } + + render() { + return ( + <> +
+ + +
+
+
+ } + type="avatar" + /> +
+
+

+ {moment().format('A')}好, + + {this.state.user.nickName || this.state.user.name} + + ,欢迎您登录系统! +

+
+ 上次IP:{this.state.user.lastLoginIp} + + + 上次登录时间:{this.state.user.lastLoginTime} + +
+
+
+ + + + 您有0封未读邮件,请尽快查收! + + + + + + + + + + + + + + + + + + + + + + + + + + ) + } +} diff --git a/Web/src/pages/home/list.jsx b/Web/src/pages/home/list.jsx new file mode 100644 index 0000000..e1884d3 --- /dev/null +++ b/Web/src/pages/home/list.jsx @@ -0,0 +1,103 @@ +import React, { Component } from 'react' +import { Card, Table } from 'antd' + +const tabList = [ + { + key: '1', + tab: '新建项目', + }, + { + key: '2', + tab: '正在签约项目', + }, + { + key: '3', + tab: '完成签约项目', + }, + { + key: '4', + tab: '项目进度', + }, +] + +const columns = [ + { + title: '区域', + dataIndex: 'area', + }, + { + title: '项目名称', + dataIndex: 'title', + }, + { + title: '户数', + dataIndex: 'count', + }, + { + title: '时间', + dataIndex: 'date', + }, +] + +const data = [ + { + key: '1', + area: '海曙区', + title: '曙光电影院地块', + count: 13, + date: '2021-01-01', + }, + { + key: '2', + area: '江北区', + title: '大庆新村地块旧城区改建项目', + count: 322, + date: '2021-01-01', + }, + { + key: '3', + area: '宁海县', + title: '桥头胡街道旧城区改造华驰文教地块', + count: 1, + date: '2021-01-01', + }, + { + key: '4', + area: '慈溪市', + title: '七二三南延道路工程', + count: 1, + date: '2021-01-01', + }, + { + key: '5', + area: '北仑区', + title: '原粮食局宿舍楼1号、2号楼(太河路北延工程)', + count: 32, + date: '2021-01-01', + }, +] + +export default class list extends Component { + + state = { + key: '1' + } + + render() { + return ( + this.setState({ key })} + > +
+ + ) + } +} diff --git a/Web/src/pages/home/notice.jsx b/Web/src/pages/home/notice.jsx new file mode 100644 index 0000000..6cbe72f --- /dev/null +++ b/Web/src/pages/home/notice.jsx @@ -0,0 +1,34 @@ +import React, { Component } from 'react' +import { Card, List } from 'antd' +import { AntIcon } from 'components' +import moment from 'moment' + +const data = [ + { title: '关于2020年度房屋征收评估机构信用考核情况的通报' }, + { title: '关于2020年度房屋征收评估机构信用考核情况的通报' }, + { title: '关于2020年度房屋征收评估机构信用考核情况的通报' }, +] + +export default class notice extends Component { + render() { + return ( + 更多} + > + ( + + } + /> + + ) + } /> + + ) + } +} diff --git a/Web/src/pages/home/statistics.jsx b/Web/src/pages/home/statistics.jsx new file mode 100644 index 0000000..a28f01b --- /dev/null +++ b/Web/src/pages/home/statistics.jsx @@ -0,0 +1,61 @@ +import React, { Component } from 'react' +import { Card, Col, Dropdown, Menu, Row, Statistic } from 'antd' +import { AntIcon } from 'components' + +export default class statistics extends Component { + render() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + 当月活跃用户 + 当年活跃用户 + + } + > + + 当月活跃用户 + + + + } + prefix={ + + } + > + + + + + + ) + } +} diff --git a/Web/src/pages/home/task.jsx b/Web/src/pages/home/task.jsx new file mode 100644 index 0000000..992f039 --- /dev/null +++ b/Web/src/pages/home/task.jsx @@ -0,0 +1,65 @@ +import React, { Component } from 'react' +import { Avatar, Card, Col, Row, Tooltip } from 'antd' + +const data = [ + { + title: '市区雷公巷地块项目选择评估机构及上传相关材料(软件开发人员)', + avatar: 'https://tb1.bdstatic.com/tb/steam.jpeg', + }, + { + title: '宁海县山河岭6号地块备案(胡靖)', + avatar: + 'https://gss0.bdstatic.com/6LZ1dD3d1sgCo2Kml5_Y_D3/sys/portrait/item/tb.1.ac342cde.2vNGrtpPcIUN6lJpSnty3g?t=1615176031', + }, + { title: '宁海县盛宁线力洋至胡陈段公路工程田交朱村 地块备案(胡靖)' }, + { + title: '慈溪市慈溪市危旧房改造一期(西门小区A1区块)项目备案(陆承)', + avatar: + 'https://gss0.bdstatic.com/6LZ1dD3d1sgCo2Kml5_Y_D3/sys/portrait/item/tb.1.54e2faca.uBtqRshdnVUXL9XFfQMTwg?t=1604074726', + }, + { title: '江北区孔浦成片危旧住宅区改造项目(六号区块)备案(成薇)' }, + { title: '镇海区宁镇路改扩建工程(庄市段)备案(董力)' }, + { title: '鄞州区茶桃公路(同谷路—金峨路延伸段)项目备案(软件开发人员)' }, + { title: '鄞州区咸祥大嵩湖工程备案(软件开发人员)' }, + { title: '江北区三官堂大桥及接线工程项目备案(成薇)' }, +] + +export default class list extends Component { + render() { + return ( + 更多}> + { + data.map((item, i) => { + return ( + + + {item.title} + + } + description={ + + { + item.avatar && + + + + } + + + 软件开发人员 + 2020-01-01 + + + + } + /> + + ) + }) + } + + ) + } +} diff --git a/Web/src/pages/system/account/base.jsx b/Web/src/pages/system/account/base.jsx new file mode 100644 index 0000000..c44719d --- /dev/null +++ b/Web/src/pages/system/account/base.jsx @@ -0,0 +1,193 @@ +import React, { Component } from 'react' +import { Button, Card, Col, Descriptions, Modal, Row, Spin, Tooltip, Upload } from 'antd' +import { AntIcon, Image } from 'components' +import { Cropper } from 'react-cropper' +import 'cropperjs/dist/cropper.css' +import { BlobToFile } from 'util/file' + +import { api } from 'common/api' + +export default class base extends Component { + state = { + img: true, + + cropperVisible: false, + loadingAvatar: false, + } + + cropper = React.createRef() + + avatarFile = null + + async onOpenAvatarCropper() { + this.setState({ cropperVisible: true }) + } + + onCloseAvatarCropper() { + this.setState({ cropperVisible: false }, () => { + setTimeout(() => { + const cropper = this.cropper.current && this.cropper.current.cropper + if (cropper) { + cropper.destroy() + } + this.avatarFile = null + this.setState({ img: true }) + }, 300) + }) + } + + onUploadAvatar() { + this.setState({ loadingAvatar: true }) + const canvas = this.cropper.current.cropper.getCroppedCanvas() + canvas.toBlob(async data => { + try { + const file = BlobToFile(data, this.avatarFile.name, this.avatarFile.type) + const fd = new FormData() + fd.append('file', file) + const { data: avatar } = await api.sysFileInfoUpload(fd) + await api.sysUserUpdateInfo({ avatar }) + this.onCloseAvatarCropper() + this.props.loadData() + } finally { + this.setState({ loadingAvatar: false }) + } + }) + } + + render() { + const { user } = this.props + + const { img, cropperVisible, loadingAvatar } = this.state + + const cropper = this.cropper.current && this.cropper.current.cropper + + return ( + <> + +
+ +
this.onOpenAvatarCropper()} + className="yo-avatar-info--cover" + > + +
+
+
+ + {user.name} + {user.nickName} + {user.account} + + {user.sex === 0 + ? '保密' + : user.sex === 1 + ? '男性' + : user.sex === 2 + ? '女性' + : '保密'} + + + {user.birthday && + (typeof user.birthday === 'string' + ? user.birthday + : user.birthday.format('YYYY-MM-DD'))} + + +
+ this.onCloseAvatarCropper()} + > + }> + + +
+ +
+ + +
+ +
+ { + this.avatarFile = file + const reader = new FileReader() + reader.readAsDataURL(file) + reader.onload = () => { + this.setState({ img: reader.result }) + } + return false + }} + showUploadList={false} + className="mr-xs" + > + + + + + + + + + + + + + + ) + } +} diff --git a/Web/src/pages/system/account/index.jsx b/Web/src/pages/system/account/index.jsx new file mode 100644 index 0000000..4ba41f6 --- /dev/null +++ b/Web/src/pages/system/account/index.jsx @@ -0,0 +1,113 @@ +import React, { Component } from 'react' +import ReactDOM from 'react-dom' +import { Anchor, Card, Col, Row, Spin } from 'antd' +import { AntIcon, Container } from 'components' +import moment from 'moment' +import store from 'store' +import { api } from 'common/api' + +import Base from './base' +import Info from './setting/info' +import Safety from './setting/satety' + +const { getState, dispatch, subscribe } = store + +const navs = [ + { title: '基本信息', component: Info }, + { title: '安全设置', component: Safety }, +] + +export default class index extends Component { + state = { + loading: false, + user: getState('user'), + } + + container = window + + constructor(props) { + super(props) + + this.unsubscribe = subscribe('user', user => { + this.setState({ user }) + }) + } + + componentDidMount() { + this.loadData() + } + + componentWillUnmount() { + this.unsubscribe() + } + + loadData = async () => { + this.setState({ loading: true }) + try { + const { data } = await api.getLoginUser() + if (data.birthday) { + data.birthday = moment(data.birthday) + } + dispatch({ + type: 'SET_USER_ACCOUNT', + user: data, + }) + } finally { + this.setState({ loading: false }) + } + } + + setContainer = container => { + this.container = (ReactDOM.findDOMNode(container) || {}).parentNode + } + + render() { + const { loadData } = this + + const { loading, user } = this.state + + return ( + } ref={this.setContainer}> + + + + this.container} + offsetTop={24} + targetOffset={100} + wrapperStyle={{ backgroundColor: 'transparent' }} + onClick={e => e.preventDefault()} + > + {navs.map((item, i) => ( + + ))} + + + + + +
+ + + + {navs.map((item, i) => ( +
+
+ + + +
+ ))} + + + + + + + ) + } +} diff --git a/Web/src/pages/system/account/setting/info.jsx b/Web/src/pages/system/account/setting/info.jsx new file mode 100644 index 0000000..8af5300 --- /dev/null +++ b/Web/src/pages/system/account/setting/info.jsx @@ -0,0 +1,87 @@ +import React, { Component } from 'react' +import { Button, DatePicker, Form, Input, message as Message, Radio } from 'antd' +import { AntIcon } from 'components' +import { api } from 'common/api' +import moment from 'moment' + +export default class index extends Component { + state = { + saving: false, + } + + form = React.createRef() + + componentDidMount() { + const { user } = this.props + if (user.birthday) { + user.birthday = moment(user.birthday) + } + this.form.current.setFieldsValue(user) + } + + async onSvaeInfo() { + this.setState({ saving: true }) + try { + await api.sysUserUpdateInfo(this.form.current.getFieldsValue()) + await this.props.loadData() + Message.success('更新个人信息成功') + } finally { + this.setState({ saving: false }) + } + } + onAvatarStart() {} + + render() { + const { user } = this.props + + const { saving } = this.state + + return ( + <> + +
+ + + + + {user.name} + + + + + + + + + 保密 + + + + + + + + + + + + + + +
+ + + + ) + } +} diff --git a/Web/src/pages/system/account/setting/satety/index.jsx b/Web/src/pages/system/account/setting/satety/index.jsx new file mode 100644 index 0000000..d5f276b --- /dev/null +++ b/Web/src/pages/system/account/setting/satety/index.jsx @@ -0,0 +1,126 @@ +import React, { Component } from 'react' +import { List } from 'antd' +import { api } from 'common/api' +import { AntIcon, ModalForm, QueryTableActions } from 'components' +import PasswordForm from './password' + +import Mail from './mail' +import Phone from './phone' + +const apiAction = { + updatePwd: api.sysUserUpdatePwd, +} + +export default class form extends Component { + updatePwdForm = React.createRef() + mailForm = React.createRef() + mhoneForm = React.createRef() + + onOpen(modal) { + modal.current.open() + } + + render() { + const { user, loadData } = this.props + + const index = [] + //密码 + index.push({ + title: '登录密码', + description: + '安全性高的密码可以使帐号更安全。建议您定期更换密码,设置一个包含字母,符号或数字中至少两项且长度超过6位的密码。', + // extra: ( + //
+ // 当前密码强度为: + // { + // [ + // , + // , + // , + // ][user.securityLevel - 1] + // } + //
+ // ), + done: true, + action: () => { + this.onOpen(this.updatePwdForm) + }, + }) + //手机 + index.push({ + title: '手机绑定', + description: ( +
+ 手机号可以直接用于登录、找回密码等。 + {user.phone && ( + <> + 您已绑定了手机{user.phone} + + )} +
+ ), + done: !!user.phone, + action: () => { + this.onOpen(this.mhoneForm) + }, + }) + //邮箱 + index.push({ + title: '邮箱绑定', + description: ( +
+ 安全邮箱可以直接用于登录、找回密码等。 + {user.email && ( + <> + 您已绑定了邮箱{user.email} + + )} +
+ ), + done: !!user.email, + action: () => { + this.onOpen(this.mailForm) + }, + }) + + return ( + <> + ( + + + + 已设置 + + 修改 + + ) : ( + + + + 未设置 + + 设置 + + ), + ]} + > + + + )} + /> + {/* */} + + {/* */} + + + + ) + } +} diff --git a/Web/src/pages/system/account/setting/satety/mail.jsx b/Web/src/pages/system/account/setting/satety/mail.jsx new file mode 100644 index 0000000..4bdb602 --- /dev/null +++ b/Web/src/pages/system/account/setting/satety/mail.jsx @@ -0,0 +1,376 @@ +import React, { Component } from 'react' +import { Form, Input, Modal, Spin, Steps, Button, Row, Col, message as Message, Select } from 'antd' +import { AntIcon } from 'components' +import { api } from 'common/api' +import { COUNT_DWON_KEY } from 'common/storage' +import store from 'store' +import { cloneDeep } from 'lodash' + +const { getState } = store + +const steps = [ + { + title: '验证', + }, + { + title: '绑定', + }, +] + +const reg = /^([a-zA-Z]|[0-9])(\w|)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/ + +const initState = { + visible: false, + loading: false, + + // 可用于验证的类型 + types: [], + // 当前步骤 + currentStep: 0, + + nextDisabled: true, + // 正在发送验证码 + sendingCode: false, + // 冷却时间 + countDown: 0, +} + +export default class form extends Component { + state = cloneDeep(initState) + + form = React.createRef() + + orgCode = '' + + //打开窗口 + open = () => { + this.setState({ visible: true }) + + this.showCountDown() + + const data = getState('user') + const types = [] + data.phone && + types.push({ + title: `使用手机号${data.phone}进行验证`, + value: 1, + }) + data.email && + types.push({ + title: `使用邮箱${data.email}进行验证`, + value: 2, + }) + + this.setState({ types }) + } + + close() { + this.setState(cloneDeep(initState)) + } + + /** + * 将倒计时添加入到本地 + */ + addTime() { + const now = Date.now() + 60 * 1000 + window.localStorage.setItem(COUNT_DWON_KEY, now) + } + /** + * 显示倒计时 + */ + showCountDown() { + const surplusTime = window.localStorage.getItem(COUNT_DWON_KEY) + const nowTime = Date.now() + if (surplusTime >= nowTime) { + this.setState({ + countDown: parseInt((surplusTime - nowTime) / 1000), + }) + setTimeout(() => { + this.showCountDown() + }, 1000) + } else { + this.setState({ countDown: 0 }) + } + } + + //发送验证码 + async onSendCode() { + const form = this.form.current + + const valid = await form.validateFields() + if (!valid) { + return + } + + this.setState({ sendingCode: true }) + + const data = form.getFieldsValue() + try { + await api.sysUserSendCode(data) + const typeName = data.type ? [, '手机', '邮箱'][data.type] : '邮箱' + Message.success(`已发送验证码到${typeName},请注意查收`) + this.addTime() + this.showCountDown() + } finally { + this.setState({ sendingCode: false }) + } + } + + // 下一步 + async onNext() { + this.setState({ loading: true }) + const data = this.form.current.getFieldsValue() + this.orgCode = data.orgCode + + try { + await api.sysUserCheckBindcode(data) + window.localStorage.removeItem(COUNT_DWON_KEY) + this.setState({ + nextDisabled: true, + currentStep: this.state.currentStep + 1, + }) + } finally { + this.setState({ loading: false }) + } + } + + // 上一步 + onPrev() { + window.localStorage.removeItem(COUNT_DWON_KEY) + this.setState({ + currentStep: this.state.currentStep - 1, + }) + } + + //完成 + async onComplete() { + this.setState({ loading: true }) + try { + await api.sysUserCheckBindcode({ + ...this.form.current.getFieldsValue(), + orgCode: this.orgCode, + }) + await this.props.loadData() + window.localStorage.removeItem(COUNT_DWON_KEY) + Message.success('绑定邮箱成功') + this.close() + } finally { + this.setState({ loading: false }) + } + } + + renderForm() { + const { nextDisabled, sendingCode, countDown } = this.state + + return ( +
+
{ + this.setState({ + nextDisabled: !(allValues.target && allValues.code), + }) + }} + > + + + + + +
+ + + + + + {countDown ? ( + + ) : ( + + )} + + + + +
+ +
+ + ) + } + + renderStepForm() { + const { types, currentStep, sendingCode, countDown } = this.state + + return ( +
+ +
+ + {steps.map(item => ( + + ))} + + + + { + this.setState({ + nextDisabled: !( + allValues.orgCode || + (allValues.target && allValues.code) + ), + }) + }} + > + {currentStep === 0 && ( + <> + + + + + + + + + + + + {countDown ? ( + + ) : ( + + )} + + + + + )} + {currentStep === 1 && ( + <> + + + + + + + + + + + + {countDown ? ( + + ) : ( + + )} + + + + + )} + +
+ {currentStep === 0 && ( + <> + + + )} + {currentStep === 1 && ( + <> + + + + )} +
+ + ) + } + + render() { + const { visible, loading, types } = this.state + + return ( + this.close()} + visible={visible} + className="yo-modal-form" + title="绑定邮箱" + > + }> +
+ {types.length ? this.renderStepForm() : this.renderForm()} +
+
+
+ ) + } +} diff --git a/Web/src/pages/system/account/setting/satety/password.jsx b/Web/src/pages/system/account/setting/satety/password.jsx new file mode 100644 index 0000000..8393f03 --- /dev/null +++ b/Web/src/pages/system/account/setting/satety/password.jsx @@ -0,0 +1,145 @@ +import React, { Component } from 'react' +import { Form, Input, message as Message, Modal } from 'antd' +import { api } from 'common/api' +import { RSA_PUBLIC_KEY } from 'util/global' +import { cloneDeep } from 'lodash' +import { Button } from 'antd/lib/radio' +import { encryptByRSA } from 'util/rsa' + +const initData = { + exist: false, + pattern: '', + descriptions: '', + visible: false, +} + +export default class form extends Component { + state = cloneDeep(initData) + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + open = () => { + this.setState({ visible: true }) + api.getPwdRule({}).then(({ success, data, message }) => { + if (success) { + const { pattern, descriptions } = data + this.setState({ + pattern, + descriptions, + }) + } else { + Message.Error(message) + } + }) + } + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + updatePwd(values) { + let { password, newPassword } = values.current.getFieldsValue() + password = encryptByRSA(password, RSA_PUBLIC_KEY) + newPassword = encryptByRSA(newPassword, RSA_PUBLIC_KEY) + const confirm = newPassword + api.sysUserUpdatePwd({ password, newPassword, confirm }).then( + ({ success, data, message }) => { + if (success) { + Message.success('密码修改完成') + this.close() + } else { + Message.warn(message) + } + } + ) + } + + close() { + this.setState(cloneDeep(initData)) + } + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData() {} + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + render() { + const { pattern, descriptions, visible } = this.state + return ( + this.close()} + onOk={() => this.updatePwd(this.form)} + visible={visible} + className="yo-modal-form" + title="更新密码" + > +
+
+ + + + + + + ({ + validator(_, value) { + if (!value || getFieldValue('newPassword') === value) { + return Promise.resolve() + } + return Promise.reject(new Error('确认新密码不匹配')) + }, + }), + ]} + name="confirm" + > + + +
+ +
+ ) + } +} diff --git a/Web/src/pages/system/account/setting/satety/phone.jsx b/Web/src/pages/system/account/setting/satety/phone.jsx new file mode 100644 index 0000000..f0bb4e9 --- /dev/null +++ b/Web/src/pages/system/account/setting/satety/phone.jsx @@ -0,0 +1,376 @@ +import React, { Component } from 'react' +import { Form, Input, Modal, Spin, Steps, Button, Row, Col, message as Message, Select } from 'antd' +import { AntIcon } from 'components' +import { api } from 'common/api' +import { COUNT_DWON_KEY } from 'common/storage' +import store from 'store' +import { cloneDeep } from 'lodash' + +const { getState } = store + +const steps = [ + { + title: '验证', + }, + { + title: '绑定', + }, +] + +const reg = /^((13[0-9])|(14[5,7])|(15[^4,\\D])|(17[0,1,3,6-8])|(18[0-9])|(19[8,9])|(166))[0-9]{8}$/ + +const initState = { + visible: false, + loading: false, + + // 可用于验证的类型 + types: [], + // 当前步骤 + currentStep: 0, + + nextDisabled: true, + // 正在发送验证码 + sendingCode: false, + // 冷却时间 + countDown: 0, +} + +export default class form extends Component { + state = cloneDeep(initState) + + form = React.createRef() + + orgCode = '' + + //打开窗口 + open = () => { + this.setState({ visible: true }) + + this.showCountDown() + + const data = getState('user') + const types = [] + data.phone && + types.push({ + title: `使用手机号${data.phone}进行验证`, + value: 1, + }) + data.email && + types.push({ + title: `使用邮箱${data.email}进行验证`, + value: 2, + }) + + this.setState({ types }) + } + + close() { + this.setState(cloneDeep(initState)) + } + + /** + * 将倒计时添加入到本地 + */ + addTime() { + const now = Date.now() + 60 * 1000 + window.localStorage.setItem(COUNT_DWON_KEY, now) + } + /** + * 显示倒计时 + */ + showCountDown() { + const surplusTime = window.localStorage.getItem(COUNT_DWON_KEY) + const nowTime = Date.now() + if (surplusTime >= nowTime) { + this.setState({ + countDown: parseInt((surplusTime - nowTime) / 1000), + }) + setTimeout(() => { + this.showCountDown() + }, 1000) + } else { + this.setState({ countDown: 0 }) + } + } + + //发送验证码 + async onSendCode() { + const form = this.form.current + + const valid = await form.validateFields() + if (!valid) { + return + } + + this.setState({ sendingCode: true }) + + const data = form.getFieldsValue() + try { + await api.sysUserSendCode(data) + const typeName = data.type ? [, '手机', '邮箱'][data.type] : '手机' + Message.success(`已发送验证码到${typeName},请注意查收`) + this.addTime() + this.showCountDown() + } finally { + this.setState({ sendingCode: false }) + } + } + + // 下一步 + async onNext() { + this.setState({ loading: true }) + const data = this.form.current.getFieldsValue() + this.orgCode = data.orgCode + + try { + await api.sysUserCheckBindcode(data) + window.localStorage.removeItem(COUNT_DWON_KEY) + this.setState({ + nextDisabled: true, + currentStep: this.state.currentStep + 1, + }) + } finally { + this.setState({ loading: false }) + } + } + + // 上一步 + onPrev() { + window.localStorage.removeItem(COUNT_DWON_KEY) + this.setState({ + currentStep: this.state.currentStep - 1, + }) + } + + //完成 + async onComplete() { + this.setState({ loading: true }) + try { + await api.sysUserCheckBindcode({ + ...this.form.current.getFieldsValue(), + orgCode: this.orgCode, + }) + await this.props.loadData() + window.localStorage.removeItem(COUNT_DWON_KEY) + Message.success('绑定手机号成功') + this.close() + } finally { + this.setState({ loading: false }) + } + } + + renderForm() { + const { nextDisabled, sendingCode, countDown } = this.state + + return ( +
+
{ + this.setState({ + nextDisabled: !(allValues.target && allValues.code), + }) + }} + > + + + + + +
+ + + + + + {countDown ? ( + + ) : ( + + )} + + + + +
+ +
+ + ) + } + + renderStepForm() { + const { types, currentStep, sendingCode, countDown } = this.state + + return ( +
+ +
+ + {steps.map(item => ( + + ))} + + + + { + this.setState({ + nextDisabled: !( + allValues.orgCode || + (allValues.target && allValues.code) + ), + }) + }} + > + {currentStep === 0 && ( + <> + + + + + + + + + + + + {countDown ? ( + + ) : ( + + )} + + + + + )} + {currentStep === 1 && ( + <> + + + + + + + + + + + + {countDown ? ( + + ) : ( + + )} + + + + + )} + +
+ {currentStep === 0 && ( + <> + + + )} + {currentStep === 1 && ( + <> + + + + )} +
+ + ) + } + + render() { + const { visible, loading, types } = this.state + + return ( + this.close()} + visible={visible} + className="yo-modal-form" + title="绑定手机" + > + }> +
+ {types.length ? this.renderStepForm() : this.renderForm()} +
+
+
+ ) + } +} diff --git a/Web/src/pages/system/app/form.jsx b/Web/src/pages/system/app/form.jsx new file mode 100644 index 0000000..f2e0fc4 --- /dev/null +++ b/Web/src/pages/system/app/form.jsx @@ -0,0 +1,133 @@ +import React, { Component } from 'react' +import { Form, Input, InputNumber, Spin } from 'antd' +import { AntIcon, ColorSelector, IconSelector } from 'components' +import { api } from 'common/api' + +const initialValues = { + sort: 100, +} + +export default class form extends Component { + state = { + // 加载状态 + loading: true, + } + + // 表单实例 + form = React.createRef() + + iconSelector = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + //#region 从后端转换成前段所需格式 + if (params.id) { + this.record = (await api.sysAppDetail({ id: params.id })).data + } + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false, + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + //#endregion + + render() { + return ( + + }> +
+ + + + + + + + + this.iconSelector.current.open( + this.form.current.getFieldValue('icon') + ) + } + /> + } + /> + + + + + + + +
+
+ + this.form.current.setFieldsValue({ + icon, + }) + } + /> + + ) + } +} diff --git a/Web/src/pages/system/app/index.jsx b/Web/src/pages/system/app/index.jsx new file mode 100644 index 0000000..4f1e853 --- /dev/null +++ b/Web/src/pages/system/app/index.jsx @@ -0,0 +1,323 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, Popconfirm, message as Message } from 'antd' +import { isEqual } from 'lodash' +import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import getDictData from 'util/dic' +import auth from 'components/authorized/handler' +import { toCamelCase } from 'util/format' +import FormBody from './form' + +// 配置页面所需接口函数 +const apiAction = { + page: api.getAppPage, + add: api.sysAppAdd, + edit: api.sysAppEdit, + delete: api.sysAppDelete, + + setDefault: api.sysAppSetAsDefault, +} + +// 用于弹窗标题 +const name = '应用' + +export default class index extends Component { + state = { + codes: { + commonStatus: [], + }, + } + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + // 表格字段 + columns = [ + { + title: '图标', + dataIndex: 'icon', + width: 32, + render: (text, record) => ( +
+ +
+ ), + }, + { + title: '应用名称', + dataIndex: 'name', + width: 300, + sorter: true, + }, + { + title: '唯一编码', + dataIndex: 'code', + width: 300, + sorter: true, + }, + { + title: '是否默认', + dataIndex: 'active', + width: 200, + sorter: true, + render: (text, record) => ( + <> + {text ? '是' : '否'} + {!record.active && ( + + + + this.onSetDefault(record)} + > + 设为默认 + + + + )} + + ), + }, + { + title: '状态', + dataIndex: 'status', + width: 100, + sorter: true, + render: text => <>{this.bindCodeValue(text, 'common_status')}, + }, + { + title: '排序', + dataIndex: 'sort', + width: 100, + sorter: true, + defaultSortOrder: 'ascend', + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ sysApp: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(this.editForm, record.id)}>编辑 + + + this.onDelete(record)} + > + 删除 + + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData('common_status').then(res => { + this.setState( + { + codes: res, + }, + () => { + onLoadData() + } + ) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id) { + modal.current.open({ + id, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + //#region 自定义方法 + async onSetDefault(record) { + this.onAction(apiAction.setDefault(record), '设置成功') + } + //#endregion + + render() { + return ( + +
+ + + + + + + + + + } + operator={ + + + + } + /> + + + + this.table.current.onReloadData()} + > + + + + + + this.table.current.onReloadData()} + > + + + +
+ ) + } +} diff --git a/Web/src/pages/system/area/form.jsx b/Web/src/pages/system/area/form.jsx new file mode 100644 index 0000000..af531b0 --- /dev/null +++ b/Web/src/pages/system/area/form.jsx @@ -0,0 +1,143 @@ +import React, { Component } from 'react' +import { Form, Input, InputNumber, Select, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import getDictData from 'util/dic' + +const initialValues = { + sort: 100, +} +export default class form extends Component { + state = { + // 加载状态 + loading: true, + exist: false, + codes: { + areacodeType: [], + }, + } + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + + const codes = await getDictData('areacode_type') + const exist = !!params.record + this.setState({ + codes, + exist, + }) + + this.record = { + ...this.record, + } + //#endregion + + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false, + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + render() { + return ( +
+ }> +
+ {/* 表单控件 */} + + + + + + + + } + > + + + + + + + + + + + {/* ... */} +
+
+ + ) + } +} diff --git a/Web/src/pages/system/area/index.jsx b/Web/src/pages/system/area/index.jsx new file mode 100644 index 0000000..adf0f75 --- /dev/null +++ b/Web/src/pages/system/area/index.jsx @@ -0,0 +1,305 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, message as Message, Popconfirm } from 'antd' +import { + AntIcon, + Auth, + Container, + ModalForm, + QueryTable, + QueryTableActions, + QueryTreeLayout, +} from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { toCamelCase } from 'util/format' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import FormBody from './form' + +const apiAction = { + tree: api.getAreaTree, + page: api.sysAreaPage, + add: api.sysAreaAdd, + edit: api.sysAreaEdit, + delete: api.sysAreaDelete, +} + +const name = '区域' + +export default class index extends Component { + state = { + codes: { + areacodeType: [], + }, + } + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + // 树选中节点 + selectCode = undefined + columns = [ + { + title: '区域类型', + dataIndex: 'levelType', + sorter: true, + width: 100, + render: text => <>{this.bindCodeValue(text, 'areacode_type')}, + }, + { + title: '区域名称', + dataIndex: 'name', + width: 100, + sorter: true, + }, + { + title: '区域编号', + dataIndex: 'code', + width: 100, + sorter: true, + }, + { + title: '行政编号', + dataIndex: 'adCode', + width: 100, + sorter: true, + defaultSortOrder: 'ascend', + }, + { + title: '描述', + dataIndex: 'note', + width: 200, + sorter: false, + }, + { + title: '排序', + dataIndex: 'sort', + width: 80, + sorter: true, + }, + ] + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ sysArea: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(this.editForm, record)}>编辑 + + + this.onDelete(record)} + > + 删除 + + + + ), + }) + } + } + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + this.table.current.onLoading() + getDictData('areacode_type').then(res => { + this.setState( + { + codes: res, + }, + () => { + this.table.current.onLoadData() + } + ) + }) + } + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + query = { + ...query, + pcode: this.selectCode, + } + //首次加载根据code列升序排序 + if (!params.sortField) { + params.sortField = 'code' + params.sortOrder = 'ascend' + } + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 调用树结构数据接口 + * [异步,必要] + * @returns + */ + loadTreeData = async () => { + const { data } = await apiAction.tree() + return data + } + + /** + * 树节点选中事件 + * [必要] + * @param {*} id + */ + onSelectTree(code) { + this.selectCode = code + this.table.current.onReloadData() + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => +p.code === code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(modal, record) { + modal.current.open({ + pcode: this.pcode, + record, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + this.table.current.onLoading() + try { + await action + Message.success(successMessage) + this.table.current.onReloadData() + } catch { + this.table.current.onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + render() { + return ( + this.onSelectTree(key)} + replaceFields={{ value: 'code', title: 'name', children: 'children' }} + > + + + + + + + + + + + } + operator={ + + + + } + > + + + this.table.current.onReloadData()} + > + + + + this.table.current.onReloadData()} + > + + + + ) + } +} diff --git a/Web/src/pages/system/config/form.jsx b/Web/src/pages/system/config/form.jsx new file mode 100644 index 0000000..0321461 --- /dev/null +++ b/Web/src/pages/system/config/form.jsx @@ -0,0 +1,134 @@ +import React, { Component } from 'react' +import { Form, Input, Radio, Select, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import getDictData from 'util/dic' + +const initialValues = {} + +export default class form extends Component { + state = { + // 加载状态 + loading: true, + + codes: { + constsType: [], + }, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式,也可以在此处调用获取详细数据接口 + const codes = await getDictData('consts_type') + this.setState({ codes }) + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false, + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + //#endregion + + render() { + const { codes } = this.state + + return ( +
+ }> +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + ) + } +} diff --git a/Web/src/pages/system/config/index.jsx b/Web/src/pages/system/config/index.jsx new file mode 100644 index 0000000..53a0b7a --- /dev/null +++ b/Web/src/pages/system/config/index.jsx @@ -0,0 +1,277 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, message as Message, Popconfirm, Tooltip } from 'antd' +import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import FormBody from './form' + +// 配置页面所需接口函数 +const apiAction = { + page: api.sysConfigPage, + add: api.sysConfigAdd, + edit: api.sysConfigEdit, + delete: api.sysConfigDelete, +} + +// 用于弹窗标题 +const name = '应用' + +// 统一配置权限标识 +const authName = 'sysConfig' + +export default class index extends Component { + state = { + codes: { + constsType: [], + }, + } + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + columns = [ + { + title: '参数名称', + dataIndex: 'name', + width: 200, + sorter: true, + }, + { + title: '唯一编码', + dataIndex: 'code', + width: 200, + sorter: true, + ellipsis: { + showTitle: false, + }, + render: text => {text}, + }, + { + title: '参数值', + dataIndex: 'value', + width: 200, + sorter: true, + }, + { + title: '所属分类', + dataIndex: 'groupCode', + width: 140, + sorter: true, + render: text => this.bindCodeValue(text, 'consts_type'), + }, + { + title: '备注', + dataIndex: 'remark', + width: 400, + sorter: true, + ellipsis: { + showTitle: false, + }, + render: text => {text}, + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ [authName]: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(this.editForm, record)}>编辑 + + + this.onDelete(record)} + > + 删除 + + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData('consts_type').then(codes => { + this.setState({ codes }, () => { + onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code === code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(modal, record) { + modal.current.open({ + record, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + //#region 自定义方法 + //#endregion + + render() { + return ( + +
+ + + + + + + + + + } + operator={ + + + + } + /> + + + + this.table.current.onReloadData()} + > + + + + + + this.table.current.onReloadData()} + > + + + +
+ ) + } +} diff --git a/Web/src/pages/system/dict/dictdata/form.jsx b/Web/src/pages/system/dict/dictdata/form.jsx new file mode 100644 index 0000000..764cd99 --- /dev/null +++ b/Web/src/pages/system/dict/dictdata/form.jsx @@ -0,0 +1,110 @@ +import React, { Component } from 'react' +import { Form, message as Message, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import MonacoEditor from 'react-monaco-editor' +import store from 'store' + +const { getState } = store + +const initialValues = {} + +export default class form extends Component { + state = { + // 加载状态 + loading: true, + ...getState('layout'), + } + + // 表单实例 + form = React.createRef() + + code = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + this.code.current.editor.setValue(this.record.extCode || '') + setTimeout(() => { + this.code.current.editor.getAction(['editor.action.formatDocument'])._run() + }, 100) + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false, + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + try { + const code = JSON.parse(this.code.current.editor.getValue()) + if (code.constructor === Object) { + postData.extCode = JSON.stringify(code) + } else { + throw new Error(0) + } + } catch { + Message.error('错误的JSON格式') + } + //#endregion + return postData + } + } + + //#region 自定义方法 + //#endregion + + render() { + const { theme } = this.state + + return ( +
+ }> +
+ +
+
+ + ) + } +} diff --git a/Web/src/pages/system/dict/dictdata/index.jsx b/Web/src/pages/system/dict/dictdata/index.jsx new file mode 100644 index 0000000..ddc22b1 --- /dev/null +++ b/Web/src/pages/system/dict/dictdata/index.jsx @@ -0,0 +1,436 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, Popconfirm, message as Message, InputNumber } from 'antd' +import { isEqual } from 'lodash' +import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import getDictData from 'util/dic' +import auth from 'components/authorized/handler' +import { toCamelCase } from 'util/format' +import FormBody from './form' + +// 配置页面所需接口函数 +const apiAction = { + page: api.sysDictDataPage, + add: api.sysDictDataAdd, + edit: api.sysDictDataEdit, + delete: api.sysDictDataDelete, + deleteBatch: api.sysDictDataDeleteBatch, +} + +// 用于弹窗标题 +const name = '字典值' + +export default class index extends Component { + state = { + codes: { + commonStatus: [], + }, + + selectedRowKeys: [], + } + + // 表格实例 + table = React.createRef() + + form = React.createRef() + + // JSON编辑窗口实例 + jsonForm = React.createRef() + + // 表格字段 + columns = [ + { + title: '文本', + dataIndex: 'value', + sorter: true, + width: 200, + render: (text, record, index) => ( + + + + ), + }, + { + title: '字典值', + dataIndex: 'code', + sorter: true, + width: 200, + render: (text, record, index) => ( + + + + ), + }, + { + title: '扩展值', + dataIndex: 'extCode', + width: 80, + align: 'center', + render: (text, record, index) => ( + <> + + + + {auth('sysDictData:edit') ? ( + this.onOpen(this.jsonForm, record)} + style={{ + fontWeight: 'bold', + display: 'inline-block', + transform: 'scaleY(.85)', + color: 'transparent', + backgroundImage: 'linear-gradient(135deg, #007bff, #52c41a)', + WebkitBackgroundClip: 'text', + }} + > + JSON + + ) : ( + <>{text} + )} + + ), + }, + { + title: '排序', + dataIndex: 'sort', + sorter: true, + width: 100, + render: (text, record, index) => ( + + + + ), + defaultSortOrder: 'ascend', + }, + { + title: '备注', + dataIndex: 'remark', + sorter: true, + render: (text, record, index) => ( + + + + ), + }, + { + title: '状态', + dataIndex: 'status', + sorter: true, + width: 80, + render: text => this.bindCodeValue(text, 'common_status'), + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ sysDictData: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record, index) => ( + + {record.id !== -1 ? ( + + this.onEdit(index)}>保存编辑 + + ) : ( + + this.onAdd(index)}>保存新增 + + )} + + this.onDelete(record)} + > + 删除 + + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + this.table.current.onLoading() + getDictData('common_status').then(res => { + this.setState( + { + codes: res, + }, + () => { + this.table.current.onLoadData() + } + ) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + query = { + ...query, + typeId: this.props.type.id, + } + + const { data } = await apiAction.page({ + ...params, + ...query, + }) + + const values = {} + data.items.forEach((item, index) => { + values[index] = item + }) + + this.form.current.setFieldsValue(values) + + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(modal, record) { + modal.current.open({ + record, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage, reload = true) { + const table = this.table.current + table.onLoading() + try { + await action + Message.success(successMessage) + if (reload) { + table.onReloadData() + } else { + table.onLoaded() + } + } catch { + table.onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + //#region 自定义方法 + onAddRow() { + const record = { + // 为了正常显示checkbox,默认给id赋予了-1 + id: -1, + value: '', + code: '', + typeId: this.props.type.id, + sort: 100, + status: 0, + remark: null, + } + const index = this.table.current.onAddRow(record) + if (index !== false) { + this.form.current.setFieldsValue({ + [index]: record, + }) + } + } + + async onAdd(index) { + const form = this.form.current + try { + await form.validateFields() + } catch (err) { + const e = err.errorFields.filter(item => item.name.includes(index)) + if (e.length) { + return + } + } + const record = form.getFieldsValue([index])[index] + // 为了正常显示checkbox,默认给id赋予了-1,在这里删除id以表示新增 + record.id = undefined + this.onAction(apiAction.add(record), '新增成功') + } + + async onEdit(index) { + const form = this.form.current + try { + await form.validateFields() + } catch (err) { + const e = err.errorFields.filter(item => item.name.includes(index)) + if (e.length) { + return + } + } + const record = form.getFieldsValue([index])[index] + this.onAction(apiAction.edit(record), '编辑成功', false) + } + + async onDeleteBatch() { + await this.onAction(apiAction.deleteBatch(this.state.selectedRowKeys), '删除成功') + + this.setState({ + selectedRowKeys: [], + }) + } + + onSaveExtCode = ({ id, extCode }) => { + const table = this.table.current, + { dataSource } = table.state, + data = dataSource.find(item => item.id === id), + index = dataSource.indexOf(data) + this.form.current.setFieldsValue({ + [index]: { + extCode, + }, + }) + dataSource[index].extCode = extCode + table.setState({ dataSource }) + } + //#endregion + + render() { + const { selectedRowKeys } = this.state + + return ( + +
+ + this.setState({ selectedRowKeys }), + getCheckboxProps: record => ({ + disabled: record.id === -1, + }), + }} + query={ + + + + + + + + + } + operator={ + + this.onDeleteBatch()} + > + + + + } + footer={() => ( + + + + )} + /> + + + + + +
+ ) + } +} diff --git a/Web/src/pages/system/dict/form.jsx b/Web/src/pages/system/dict/form.jsx new file mode 100644 index 0000000..e80f2ec --- /dev/null +++ b/Web/src/pages/system/dict/form.jsx @@ -0,0 +1,166 @@ +import React, { Component } from 'react' +import { Form, Input, Radio, InputNumber, TreeSelect, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import getDictData from 'util/dic' +import { EMPTY_ID } from 'util/global' +import { api } from 'common/api' + +const initialValues = { + type: 1, + sort: 100 +} +export default class form extends Component { + + state = { + // 加载状态 + loading: true, + options: { + dicTreeData: [] + } + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + const treeData = await this.loadDicTreeData() + this.setState({ + options: { + dicTreeData: treeData + } + }) + + this.record = { + pid: params.pid, + ...this.record + } + + if (this.record.code) { + this.record.type = 2; + this.setState({ + type: 2 + }) + } + + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + //#region 自定义方法 + async loadDicTreeData() { + const { data } = await api.sysDictTypeTree() + return [{ + id: EMPTY_ID, + parentId: undefined, + title: '顶级', + value: EMPTY_ID, + pid: undefined, + children: data, + }] + } + + //#endregion + + render() { + return ( +
{ + if (changeValues.hasOwnProperty('type')) { + this.setState({ + type: changeValues.type + }) + } + }} + > + }> +
+ {/* 表单控件 */} + + + + + + + 目录 + + + 字典类型 + + + + + + + {this.state.type == 2 && + <> + + + + + } + + + + + + + {/* ... */} +
+
+ + ) + } +} diff --git a/Web/src/pages/system/dict/index.jsx b/Web/src/pages/system/dict/index.jsx new file mode 100644 index 0000000..8c46ab5 --- /dev/null +++ b/Web/src/pages/system/dict/index.jsx @@ -0,0 +1,324 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, message as Message, Popconfirm, Radio } from 'antd' +import { + AntIcon, + Auth, + Container, + ModalForm, + QueryTable, + QueryTableActions, + QueryTreeLayout, +} from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { toCamelCase } from 'util/format' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import FormBody from './form' +import DictData from './dictdata' + +const apiAction = { + tree: api.sysDictTypeTree, + page: api.sysDictTypePage, + add: api.sysDictTypeAdd, + edit: api.sysDictTypeEdit, + delete: api.sysDictTypeDelete, +} + +const name = '字典' + +export default class index extends Component { + state = { + codes: { + commonStatus: [], + }, + } + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + // 树选中节点 + selectId = undefined + + // 表格字段 + columns = [ + { + title: '字典名称', + dataIndex: 'name', + width: 200, + sorter: true, + }, + { + title: '类型', + key: 'type', + dataIndex: 'code', + width: 120, + sorter: true, + render: text => (text ? '字典类型' : '目录'), + }, + { + title: '唯一编码', + dataIndex: 'code', + width: 120, + sorter: true, + }, + { + title: '排序', + dataIndex: 'sort', + width: 80, + sorter: true, + defaultSortOrder: 'ascend', + }, + { + title: '备注', + dataIndex: 'remark', + width: 200, + sorter: true, + }, + { + title: '状态', + dataIndex: 'status', + width: 80, + sorter: true, + render: text => this.bindCodeValue(text, 'common_status'), + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ sysDict: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(this.editForm, record)}>编辑 + + + this.onDelete(record)} + > + 删除 + + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + this.table.current.onLoading() + getDictData('common_status').then(res => { + this.setState( + { + codes: res, + }, + () => { + this.table.current.onLoadData() + } + ) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + query = { + ...query, + pid: this.selectId, + } + + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 调用树结构数据接口 + * [异步,必要] + * @returns + */ + loadTreeData = async () => { + const { data } = await apiAction.tree() + return data + } + + /** + * 树节点选中事件 + * [必要] + * @param {*} id + */ + onSelectTree(id) { + this.selectId = id + this.table.current.onReloadData() + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(modal, record) { + modal.current.open({ + pid: this.selectId, + record, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + this.table.current.onLoading() + try { + await action + Message.success(successMessage) + this.table.current.onReloadData() + } catch { + this.table.current.onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + //#region 自定义方法 + //#endregion + + render() { + return ( + this.onSelectTree(key)} + > + + + + + + + + + + + + 目录 + 字典类型 + + + + } + operator={ + + + + } + expandable={{ + expandedRowRender: record => , + rowExpandable: record => !!record.code, + }} + /> + + + this.table.current.onReloadData()} + > + + + + this.table.current.onReloadData()} + > + + + + ) + } +} diff --git a/Web/src/pages/system/doc/back-end/application/dto.jsx b/Web/src/pages/system/doc/back-end/application/dto.jsx new file mode 100644 index 0000000..c258272 --- /dev/null +++ b/Web/src/pages/system/doc/back-end/application/dto.jsx @@ -0,0 +1,35 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Text } = Typography + +export default class dto extends Component { + render() { + return ( + + + 数据传输类型直接将前端的JSON序列化为实体类,可以在接口中很方便地获取到前端带过来的参数。 + + + 放置到 + + Service/区域(可无)/业务模块/Dto + + 文件夹下。 + + + 推荐将输入类型和输出类型区分,既输入Dto + + Input + + 和输出Dto + + Output + + + + + ) + } +} diff --git a/Web/src/pages/system/doc/back-end/application/entity.jsx b/Web/src/pages/system/doc/back-end/application/entity.jsx new file mode 100644 index 0000000..d7cf886 --- /dev/null +++ b/Web/src/pages/system/doc/back-end/application/entity.jsx @@ -0,0 +1,33 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Text } = Typography + +export default class entity extends Component { + render() { + return ( + + + 实体关系到数据库表的建立以及映射,放置到 + + Entity + + 文件夹下。 + + + 需要注意的是,必须添加特性 + + Table + + ;必须继承类 + + Core.DEntityBase + + 。 + + + + ) + } +} diff --git a/Web/src/pages/system/doc/back-end/application/index.jsx b/Web/src/pages/system/doc/back-end/application/index.jsx new file mode 100644 index 0000000..96a9837 --- /dev/null +++ b/Web/src/pages/system/doc/back-end/application/index.jsx @@ -0,0 +1,35 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' + +const { Text } = Typography + +export default class index extends Component { + render() { + return ( + + + 所有业务实现均写在Ewide.Application类库中 + + 其中 +
    +
  • +

    + Entity存放业务相关表实体类。 +

    +
  • +
  • +

    + Enum存放业务相关枚举。 +

    +
  • +
  • +

    + Service存放业务接口及其实现。 +

    +
  • +
+ 在这里创建一个简单的业务实现。 +
+ ) + } +} diff --git a/Web/src/pages/system/doc/back-end/application/interface.jsx b/Web/src/pages/system/doc/back-end/application/interface.jsx new file mode 100644 index 0000000..89c5e24 --- /dev/null +++ b/Web/src/pages/system/doc/back-end/application/interface.jsx @@ -0,0 +1,19 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Text } = Typography + +export default class _interface extends Component { + render() { + return ( + + 定义接口,具体作用我也不熟悉,推荐后端来写此说明。 + + + ) + } +} diff --git a/Web/src/pages/system/doc/back-end/application/service.jsx b/Web/src/pages/system/doc/back-end/application/service.jsx new file mode 100644 index 0000000..84be861 --- /dev/null +++ b/Web/src/pages/system/doc/back-end/application/service.jsx @@ -0,0 +1,40 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Text } = Typography + +export default class service extends Component { + render() { + return ( + + + 须要继承三个接口,首先是上文定义的业务相关接口 + + Interface + + ,动态Api控制器依赖接口 + + IDynamicApiController + + 以及瞬时服务注册依赖 + + ITransient + + 。 + + + 特性 + + ApiDescriptionSettings + + 用于Swagger显示接口,非必须。 + + + + ) + } +} diff --git a/Web/src/pages/system/doc/back-end/database/index.jsx b/Web/src/pages/system/doc/back-end/database/index.jsx new file mode 100644 index 0000000..5d095ca --- /dev/null +++ b/Web/src/pages/system/doc/back-end/database/index.jsx @@ -0,0 +1,7 @@ +import React, { Component } from 'react' + +export default class index extends Component { + render() { + return
+ } +} diff --git a/Web/src/pages/system/doc/back-end/database/migrations.jsx b/Web/src/pages/system/doc/back-end/database/migrations.jsx new file mode 100644 index 0000000..56e55bc --- /dev/null +++ b/Web/src/pages/system/doc/back-end/database/migrations.jsx @@ -0,0 +1,83 @@ +import React, { Component } from 'react' +import { Breadcrumb, Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Title, Text } = Typography + +export default class migrations extends Component { + render() { + return ( + + 在建立或修改了实体类之后,可以将实体类更新到数据库。 + + 在Visual Studio中选择 + + 工具 + NuGet 包管理器 + 程序包管理控制台 + + 之后在打开的程序包管理控制台中,默认项目选择 + + Ewide.Database.Migrations + + ,并在控制台中输入命令。 + + 生成迁移文件 + + 其中 +
    +
  • +

    + add-migration是固定的命令。 +

    +
  • +
  • +

    + init是自定义的数据库版本号。 +

    +
  • +
  • +

    + -c是固定的参数。 +

    +
  • +
  • +

    + 'DefaultDbContext'是对应的DbContext。 +

    +
  • +
+

更新迁移到数据库

+ + 在确保 + + Ewide.Database.Migrations + + 中已经生成迁移文件之后,可以运行更新命令。 + + + 其中 +
    +
  • +

    + update-database是固定的命令。 +

    +
  • +
  • +

    + -context是固定的参数。 +

    +
  • +
  • +

    + 'DefaultDbContext'是对应的DbContext。 +

    +
  • +
+
+ ) + } +} diff --git a/Web/src/pages/system/doc/back-end/index.jsx b/Web/src/pages/system/doc/back-end/index.jsx new file mode 100644 index 0000000..ea9a23c --- /dev/null +++ b/Web/src/pages/system/doc/back-end/index.jsx @@ -0,0 +1,157 @@ +import React, { Component } from 'react' +import { Anchor, Card, Col, Row, Typography } from 'antd' +import { Container } from 'components' + +import Database from './database' +import DatabaseMigrations from './database/migrations' + +import Application from './application' +import ApplicationEntity from './application/entity' +import ApplicationDto from './application/dto' +import ApplicationInterface from './application/interface' +import ApplicationService from './application/service' + +const { Title, Link } = Typography + +const docs = [ + { + title: '数据库', + component: Database, + children: [ + { + title: '实体(N)', + }, + { + title: '迁移', + component: DatabaseMigrations, + }, + ], + }, + { + title: '业务实现', + component: Application, + children: [ + { + title: '实体', + component: ApplicationEntity, + }, + { + title: '数据传输对象', + component: ApplicationDto, + }, + { + title: '接口', + component: ApplicationInterface, + }, + { + title: '业务实现', + component: ApplicationService, + }, + ], + }, +] + +export default class index extends Component { + container = window + + setContainer = container => { + this.container = (container || { parentNode: window }).parentNode + } + + render() { + return ( +
+ + + +
+ + + + + + + + + + +
+ + + + {docs.map((item, i) => ( + +
+ {item.title} + {item.component && ( + + )} + {item.children && ( + <> +
+
+ {item.children.map((citem, ci) => ( + +
+ + {citem.title} + + {citem.component && ( + + )} +
+
+
+ ))} + + )} +
+
+
+ ))} +
+ + + this.container} + offsetTop={24} + onClick={e => e.preventDefault()} + > + {docs.map((item, i) => ( + + {item.children && + item.children.map((citem, ci) => ( + + ))} + + ))} + + + + {this.props.supportInfo} + + + + ) + } +} diff --git a/Web/src/pages/system/doc/front-end/api/index.jsx b/Web/src/pages/system/doc/front-end/api/index.jsx new file mode 100644 index 0000000..98d40ae --- /dev/null +++ b/Web/src/pages/system/doc/front-end/api/index.jsx @@ -0,0 +1,14 @@ +import React, { Component } from 'react' +import { Space, Typography } from 'antd' + +const { Text } = Typography + +export default class index extends Component { + render() { + return ( + + 在本框架中,只需要进行简单的接口配置,就可以实现调用。 + + ) + } +} diff --git a/Web/src/pages/system/doc/front-end/api/setting.jsx b/Web/src/pages/system/doc/front-end/api/setting.jsx new file mode 100644 index 0000000..7b7f179 --- /dev/null +++ b/Web/src/pages/system/doc/front-end/api/setting.jsx @@ -0,0 +1,26 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Text } = Typography + +export default class setting extends Component { + render() { + return ( + + + 维护接口地址在 + + /src/common/api/requests + + 中,通过每个子目录的 + + index.js + + 引入 + + + + ) + } +} diff --git a/Web/src/pages/system/doc/front-end/api/usage.jsx b/Web/src/pages/system/doc/front-end/api/usage.jsx new file mode 100644 index 0000000..3f5cb04 --- /dev/null +++ b/Web/src/pages/system/doc/front-end/api/usage.jsx @@ -0,0 +1,17 @@ +import React, { Component } from 'react' +import { Space, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Text } = Typography + +export default class usage extends Component { + render() { + return ( + + 调用时需先引入。 + 接口的函数名对应到上面配置的接口名称即可。 + + + ) + } +} diff --git a/Web/src/pages/system/doc/front-end/auth/index.jsx b/Web/src/pages/system/doc/front-end/auth/index.jsx new file mode 100644 index 0000000..977f6e5 --- /dev/null +++ b/Web/src/pages/system/doc/front-end/auth/index.jsx @@ -0,0 +1,27 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Text } = Typography + +export default class index extends Component { + render() { + return ( + + + 在不少业务组件中,时常需要做到权限控制一些按钮的显示隐藏。如果只使用样式来隐藏按钮,是不安全的。 + + 所以在本框架中,推荐将按钮用全局的权限组件包裹来控制是否渲染。 + + + 权限标识会读取全局用户信息中的 + + permissions + + 进行比对。 + + 权限组件内可以是任何元素,只要不符合条件,将不会渲染。 + + ) + } +} diff --git a/Web/src/pages/system/doc/front-end/index.jsx b/Web/src/pages/system/doc/front-end/index.jsx new file mode 100644 index 0000000..27a816c --- /dev/null +++ b/Web/src/pages/system/doc/front-end/index.jsx @@ -0,0 +1,213 @@ +import React, { Component } from 'react' +import { Anchor, Card, Col, Row, Typography } from 'antd' +import { Container } from 'components' + +import Window from './window' +import WindowOpen from './window/open' +import WindowClose from './window/close' +import WindowReload from './window/reload' + +import Api from './api' +import ApiSetting from './api/setting' +import ApiUsage from './api/usage' + +import Auth from './auth' + +import Seed from './seed' + +import Util from './util' +import UtilStyle from './util/style' +import UtilDict from './util/dict' +import UtilGlobal from './util/global' +import UtilFormat from './util/format' +import UtilFile from './util/file' +import UtilQuery from './util/query' + +const { Title, Link } = Typography + +const docs = [ + { + title: '窗口', + component: Window, + children: [ + { title: '打开窗口', component: WindowOpen }, + { title: '关闭窗口', component: WindowClose }, + { title: '重新加载窗口', component: WindowReload }, + ], + }, + { + title: '接口', + component: Api, + children: [ + { title: '配置', component: ApiSetting }, + { title: '调用', component: ApiUsage }, + ], + }, + { + title: '权限渲染', + component: Auth, + }, + { + title: '种子模版', + component: Seed, + }, + { + title: '工具', + component: Util, + children: [ + { title: '通用样式', component: UtilStyle }, + { title: '读取字典', component: UtilDict }, + { title: '全局常量', component: UtilGlobal }, + { title: '字符串格式转化', component: UtilFormat }, + { title: '文件', component: UtilFile }, + { title: '查询相关', component: UtilQuery }, + ], + }, +] + +export default class index extends Component { + container = window + + setContainer = container => { + this.container = (container || { parentNode: window }).parentNode + } + + render() { + return ( +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + {docs.map((item, i) => ( + +
+ {item.title} + {item.component && ( + + )} + {item.children && ( + <> +
+
+ {item.children.map((citem, ci) => ( + +
+ + {citem.title} + + {citem.component && ( + + )} +
+
+
+ ))} + + )} +
+
+
+ ))} +
+ + + this.container} + offsetTop={24} + onClick={e => e.preventDefault()} + > + {docs.map((item, i) => ( + + {item.children && + item.children.map((citem, ci) => ( + + ))} + + ))} + + + + {this.props.supportInfo} + + + + ) + } +} diff --git a/Web/src/pages/system/doc/front-end/seed/index.jsx b/Web/src/pages/system/doc/front-end/seed/index.jsx new file mode 100644 index 0000000..376d87b --- /dev/null +++ b/Web/src/pages/system/doc/front-end/seed/index.jsx @@ -0,0 +1,70 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' + +const { Title, Text } = Typography + +export default class index extends Component { + render() { + return ( + + + 种子模版已经提供了业务组件通用的架构,可以在{' '} + + /seed + + 中获取架构代码。 + + 模版解释 +
    +
  • + /seed/query-table/index.jsx + 通用查询表格模版。 +
  • +
  • + /seed/query-table-form/form.jsx + 简单的编辑弹出框内表单,配合外部使用 + + modal-form + + 。 +
  • +
  • + /seed/form +
      +
    • + /index.jsx + 大型表单主页。可在此调用数据详情接口并分配给各个分片表单,统合分片表单。 +
    • +
    • + /part.jsx + 大型表单分片。主要考虑到一个文件中维护的表单字段过多,所以在此拆分。 +
    • +
    +
  • +
  • + /seed/form-tabe +
      +
    • + /index.jsx + 大型标签页-表单主页。可在此调用数据详情接口并分配给各个标签页及以下分片表单。 +
    • +
    • + /tab +
        +
      • + /index.jsx + 单个标签页内主页,在此统合分片表单并传递数据。 +
      • +
      • + /part.jsx + 大型表单分片。同上。 +
      • +
      +
    • +
    +
  • +
+
+ ) + } +} diff --git a/Web/src/pages/system/doc/front-end/util/dict.jsx b/Web/src/pages/system/doc/front-end/util/dict.jsx new file mode 100644 index 0000000..be5892d --- /dev/null +++ b/Web/src/pages/system/doc/front-end/util/dict.jsx @@ -0,0 +1,22 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Text } = Typography + +export default class dict extends Component { + render() { + return ( + + + /dic + getDictData + + + 通过传入字典编码快速查找并返回字典数据。默认从Redux中读取,如果Redux中不存在,则会调用接口从数据库获取。需要注意的是,获取字典所需的是下划线形式的编码,而返回的字典JSON中键名称必定为驼峰形式。 + + + + ) + } +} diff --git a/Web/src/pages/system/doc/front-end/util/file.jsx b/Web/src/pages/system/doc/front-end/util/file.jsx new file mode 100644 index 0000000..ef74d07 --- /dev/null +++ b/Web/src/pages/system/doc/front-end/util/file.jsx @@ -0,0 +1,90 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' + +const { Text } = Typography + +export default class file extends Component { + render() { + return ( + + + /file + +
    +
  • +

    + ArrayBufferToBase64 + (arrayBuffer: ArrayBuffer) => String + ArrayBuffer转成Base64。 +

    +
  • +
  • +

    + ArrayBufferToBlob + (arrayBuffer: ArrayBuffer) => Blob + ArrayBuffer转成Blob。 +

    +
  • +
  • +

    + Base64ToBlob + (base64: String) => Blob + Base64转成Blob。 +

    +
  • +
  • +

    + BlobToBase64 + (blob: Blob) => String + Blob转成Base64。 +

    +
  • +
  • +

    + BlobToFile + + (blob: Blob, fileName: String, fileType: String) => File + + Blob转成File对象。 +

    +
  • +
  • +

    + Base64ToFile + (base64: String, fileName: String) => File + Base64转成File对象。 +

    +
  • +
  • +

    + PreviewFileResponse + (id: String) => Response + 根据文件ID从接口获取文件信息。 +

    +
  • +
  • +

    + PreviewFileArrayBuffer + (id: String) => ArrayBuffer + 根据文件ID从接口获取文件ArrayBuffer。 +

    +
  • +
  • +

    + PreviewFileBase64 + (id: String) => String + 根据文件ID从接口获取文件Base64。 +

    +
  • +
  • +

    + PreviewFile + (id: String) => File + 根据文件ID从接口获取文件对象。 +

    +
  • +
+
+ ) + } +} diff --git a/Web/src/pages/system/doc/front-end/util/format.jsx b/Web/src/pages/system/doc/front-end/util/format.jsx new file mode 100644 index 0000000..9aa3b2c --- /dev/null +++ b/Web/src/pages/system/doc/front-end/util/format.jsx @@ -0,0 +1,39 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' + +const { Text } = Typography + +export default class format extends Component { + render() { + return ( + + + /format + +
    +
  • +

    + numberToChinese + (number: [Number, String]) => String + 将数字转换为中文数字。 +

    +
  • +
  • +

    + toCamelCase + (str: String) => String + 下划线转驼峰。 +

    +
  • +
  • +

    + toUnderScoreCase + (str: String) => String + 驼峰转下划线。 +

    +
  • +
+
+ ) + } +} diff --git a/Web/src/pages/system/doc/front-end/util/global.jsx b/Web/src/pages/system/doc/front-end/util/global.jsx new file mode 100644 index 0000000..2dddacb --- /dev/null +++ b/Web/src/pages/system/doc/front-end/util/global.jsx @@ -0,0 +1,54 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' + +const { Text } = Typography + +export default class global extends Component { + render() { + return ( + + + /global + +
    +
  • +

    + EMPTY_ID + 一个空GUID字符串,一般用于判断树节点的顶层。 +

    +
  • +
  • +

    + PERVIEW_URL + 文件预览地址,只在文件预览接口开放匿名后可以直接通过src引用。 +

    +
  • +
  • +

    + RSA_PUBLIC_KEY + 前后端非对称加密的公钥。 +

    +
  • +
  • +

    + AMAP_WEBAPI_KEY + 高德地图Webapi所使用的key。 +

    +
  • +
  • +

    + CITY + 城市名称,一般用于地图定位城市。 +

    +
  • +
  • +

    + SIDER_BREAK_POINT + 响应式小屏幕响应宽度。 +

    +
  • +
+
+ ) + } +} diff --git a/Web/src/pages/system/doc/front-end/util/index.jsx b/Web/src/pages/system/doc/front-end/util/index.jsx new file mode 100644 index 0000000..47b336b --- /dev/null +++ b/Web/src/pages/system/doc/front-end/util/index.jsx @@ -0,0 +1,21 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Title, Text } = Typography + +export default class index extends Component { + render() { + return ( + + + 在 + + /util + + 中,默认已提供了许多工具函数。也可以自行进行添加。 + + + ) + } +} diff --git a/Web/src/pages/system/doc/front-end/util/query.jsx b/Web/src/pages/system/doc/front-end/util/query.jsx new file mode 100644 index 0000000..7bdf762 --- /dev/null +++ b/Web/src/pages/system/doc/front-end/util/query.jsx @@ -0,0 +1,119 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Text } = Typography + +export default class query extends Component { + render() { + return ( + + + /query + +
    +
  • + + + QueryType + 获取查询类型字符串,与后端对应。 + +
      +
    • +

      + GreaterThan:{' '} + > +

      +
    • +
    • +

      + GreaterThanOrEqual:{' '} + >= +

      +
    • +
    • +

      + LessThan:{' '} + < +

      +
    • +
    • +

      + LessThanOrEqual:{' '} + <= +

      +
    • +
    • +

      + LikeLIKE +

      +
    • +
    • +

      + Equal= +

      +
    • +
    • +

      + StartWith:{' '} + STRAT +

      +
    • +
    • +

      + EndWithEND +

      +
    • +
    +
    +
  • +
  • + + + getSearchDateRange + + (range: Array, format: String = 'YYYY-MM-DD', unit: String = + 'days') => Array + + + 获取查询用时间范围数组,在这里会自动将第二个时间增加1天。 + + 如果选择的日期范围为2021-01-01~2021-01-10,最终需要取得 + >=2021-01-01 and <2021-01-11 的结果 + + +
  • +
  • + + + getSearchInfo + + ({'{'}query: Object, queryType: Object{'}'}) => Array + + + 从键值对的query类型转换成数组类型。 + 键:自动作为field值 + 值:得到一个数组作为value的值 + + queryType:一个JSON类型,已query的键为键, + + QueryType + + 为值。 如果是一个 + + QueryType + + 的数组,则自动对应到value中的各个值。 + + 示例: + + +
  • +
+
+ ) + } +} diff --git a/Web/src/pages/system/doc/front-end/util/style.jsx b/Web/src/pages/system/doc/front-end/util/style.jsx new file mode 100644 index 0000000..c20edd2 --- /dev/null +++ b/Web/src/pages/system/doc/front-end/util/style.jsx @@ -0,0 +1,197 @@ +import React, { Component } from 'react' +import { Space, Tag, Tooltip, Typography } from 'antd' + +const { Text } = Typography + +export default class style extends Component { + render() { + return ( + + 在标签中使用一些已经定义好的class可以快速调整你想要的样式。 +
    +
  • + + + 字体大小 + + /lib/font-size.less + + + + + .h1 + + + .h2 + + + .h3 + + ... + + + + .f-12 + + + .f-13 + + + .f-14 + + ... + + +
  • +
  • + + + 文字颜色 + + /lib/text-color.less + + + + + .text-primary + + + .text-info + + + .text-success + + + .text-error.text-danger + + + .text-warning + + + .text-white + + + .text-black + + + .text-gray + + + .text-normal + + ... + + +
  • +
  • + + + 显示 + + /lib/visibility.less + + + + + .hide + + + .hidden + + + .block + + + .inline-block + + + .inline + + + .flex + + + .inline-flex + + + .ellipsis + + + .ellipsis-2 + + + .ellipsis-3 + + ... + + +
  • +
  • + + + 内外边距 + + /lib/margin.less + + + + + .m-none + + + .m-xxs + + + .m-xs + + + .m-sm + + + .m-md + + + .m-lg + + + .m-xl + + ... + + + + .mt-md + + ... + + + + .mr-md + + ... + + + + .mb-md + + ... + + + + .ml-md + + ... + + + + .p-md + + ... + + +
  • +
+
+ ) + } +} diff --git a/Web/src/pages/system/doc/front-end/window/close.jsx b/Web/src/pages/system/doc/front-end/window/close.jsx new file mode 100644 index 0000000..ff865d2 --- /dev/null +++ b/Web/src/pages/system/doc/front-end/window/close.jsx @@ -0,0 +1,21 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Title, Text } = Typography + +export default class close extends Component { + render() { + return ( + + 可调用全局方法关闭窗口 + + + + String | Number + 非必传参数。指定关闭窗口的键。如果未指定键,则关闭当前选中的窗口。 + + + ) + } +} diff --git a/Web/src/pages/system/doc/front-end/window/index.jsx b/Web/src/pages/system/doc/front-end/window/index.jsx new file mode 100644 index 0000000..ebc80db --- /dev/null +++ b/Web/src/pages/system/doc/front-end/window/index.jsx @@ -0,0 +1,28 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' + +const { Text } = Typography + +export default class index extends Component { + render() { + return ( + + + 本框架是以 + 页签 + 形式打开业务组件(一般意义上的页面)。当然其中也有使用到路由,但只用于登录与主页的跳转。 + + + 所有业务组件都放置于 + + /src/pages + + 目录下,打开窗口时默认只读取该目录下的组件。 + + + 目前已对打开和关闭窗口的方法进行了全局化处理,可以在任何组件内轻松地使用。 + + + ) + } +} diff --git a/Web/src/pages/system/doc/front-end/window/open.jsx b/Web/src/pages/system/doc/front-end/window/open.jsx new file mode 100644 index 0000000..43b9651 --- /dev/null +++ b/Web/src/pages/system/doc/front-end/window/open.jsx @@ -0,0 +1,80 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Title, Text } = Typography + +export default class open extends Component { + render() { + return ( + + 可调用全局方法打开窗口 + + 配置 +
    +
  • +

    + key + String | Number + 非必要,窗口的唯一键。当下一次打开同键的窗口时,只切换到该窗口。如果未指定唯一键,将会自动生成随机键。 +

    +
  • +
  • +

    + icon + String非必要,窗口页签的图标。 +

    +
  • +
  • +

    + title + String + 非必要,窗口页签的标题。如果不指定标题,讲会以“新建窗口”显示。 +

    +
  • +
  • +

    + subTitle + String + 非必要,窗口页签的副标题。 +

    +
  • +
  • +

    + path + String必要,组件路径。 +

    +
  • +
  • +

    + param + Object非必要,传递参数。在业务组件中通过 + + props + + 接收。 +

    +
  • +
  • +

    + openType + Number + 打开类型:1、组件,2、iframe,3、新浏览器窗口。 +

    +
  • +
  • +

    + closable + Boolean非必要,设置是否可关闭窗口。默认为可关闭。 +

    +
  • +
+ 通过菜单名打开窗口 + +
+ ) + } +} diff --git a/Web/src/pages/system/doc/front-end/window/reload.jsx b/Web/src/pages/system/doc/front-end/window/reload.jsx new file mode 100644 index 0000000..add4e72 --- /dev/null +++ b/Web/src/pages/system/doc/front-end/window/reload.jsx @@ -0,0 +1,21 @@ +import React, { Component } from 'react' +import { Space, Tag, Typography } from 'antd' +import Highlighter from 'pages/system/doc/highlighter' + +const { Title, Text } = Typography + +export default class reload extends Component { + render() { + return ( + + 可调用全局方法刷新窗口 + + + + String | Number + 非必传参数。指定刷新窗口的键。如果未指定键,则刷新当前选中的窗口。 + + + ) + } +} diff --git a/Web/src/pages/system/doc/highlighter.jsx b/Web/src/pages/system/doc/highlighter.jsx new file mode 100644 index 0000000..cf86115 --- /dev/null +++ b/Web/src/pages/system/doc/highlighter.jsx @@ -0,0 +1,88 @@ +import React, { Component } from 'react' +import { Button, message as Message, Space } from 'antd' +import MonacoEditor from 'react-monaco-editor' + +const baseCopy = content => { + try { + const $textarea = document.createElement('textarea') + $textarea.style = 'opacity: 0;position: fixed;top: -10000;left: -10000' + document.body.append($textarea) + $textarea.value = content + $textarea.select() + document.execCommand('copy') + $textarea.remove() + Message.success('已复制到剪贴板') + } catch { + Message.error('复制失败') + } +} + +const copy = code => { + baseCopy(code) +} + +const copyTemplate = code => { + code = + '"' + + code + // 转义双引号 => \" + .replace(/"/g, '\\"') + // 转义$ => $$ + .replace(/\$/g, '$$$$') + // 替换行首 => " + .replace(/\n/g, '"') + // 替换行末 = ", + .replace(/\r/g, '",\r') + + '"' + let flag = true + while (flag) { + const p = code.match(/\$\${.*?}/) + if (p && p[0]) { + code = code.replace(p[0], `$\{${p[0].slice(1)}}`) + } else { + flag = false + } + } + baseCopy(code) +} + +export default class highlighter extends Component { + onEditorDidMount(editor) { + editor.setValue(this.props.code) + } + + render() { + const { code, copyTemplate } = this.props + + const line = code.split('\r\n').length + const height = line > 20 ? 400 : line * 20 + + return ( +
+ this.onEditorDidMount(editor)} + /> +
+ + {copyTemplate && ( + + )} + + +
+
+ ) + } +} diff --git a/Web/src/pages/system/doc/index.jsx b/Web/src/pages/system/doc/index.jsx new file mode 100644 index 0000000..e295d47 --- /dev/null +++ b/Web/src/pages/system/doc/index.jsx @@ -0,0 +1,78 @@ +import React, { Component } from 'react' +import { Tabs } from 'antd' +import BackEnd from './back-end' +import FrontEnd from './front-end' + +const tabs = [ + { + title: '后端', + component: BackEnd, + }, + { + title: '前端', + component: FrontEnd, + }, +] + +export default class index extends Component { + state = { + activeKey: '0', + } + + codes = {} + + constructor(props) { + super(props) + + // 读取doc-code下所有文件内容 + const files = require.context( + '../../../../public/doc-code', + true, + /\.(txt|js|jsx|html|vue|css|less|json|cs)(\?.*)?$/ + ) + const codes = {} + files.keys().forEach(p => { + const filepath = p.slice(2) + const xhr = new XMLHttpRequest() + xhr.open('GET', `./doc-code/${filepath}`, false) + xhr.overrideMimeType('text/plain;charset=utf-8') + xhr.send(null) + codes[filepath] = xhr.responseText + }) + + this.codes = codes + } + + render() { + const { activeKey } = this.state + + return ( +
+
+
+ this.setState({ activeKey })} centered> + {tabs.map((item, i) => ( + + ))} + +
+ {tabs.map((item, i) => ( +
+ +
+ ))} +
+
+
+
+ ) + } +} diff --git a/Web/src/pages/system/file/index.jsx b/Web/src/pages/system/file/index.jsx new file mode 100644 index 0000000..f5db6e0 --- /dev/null +++ b/Web/src/pages/system/file/index.jsx @@ -0,0 +1,398 @@ +import React, { Component } from 'react' +import { + Button, + Card, + Form, + Input, + message as Message, + Popconfirm, + Select, + Tag, + Tooltip, + Upload, +} from 'antd' +import { AntIcon, Auth, Container, PhotoPreview, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import { ArrayBufferToBase64, GetFileName, PreviewFileArrayBuffer } from 'util/file' + +/** + * 注释段[\/**\/]为必须要改 + */ + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api.sysFileInfoPage, + delete: api.sysFileInfoDelete, +} + +/** + * 用于弹窗标题 + * [必要] + */ +const name = '/**/' + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = 'sysFileInfo' + +export default class index extends Component { + state = { + codes: { + fileStorageLocation: [], + }, + + uploading: false, + } + + // 表格实例 + table = React.createRef() + + photoPreview = React.createRef() + + columns = [ + { + title: '文件名称', + dataIndex: 'fileOriginName', + width: 300, + ellipsis: { + showTitle: false, + }, + sorter: true, + render: text => {text}, + }, + { + title: '文件后缀', + dataIndex: 'fileSuffix', + width: 120, + sorter: true, + render: text => {text}, + }, + { + title: '文件大小', + dataIndex: 'fileSizeKb', + width: 120, + sorter: true, + render: text => ( + <> + {text} + KB + + ), + }, + { + title: '存储位置', + dataIndex: 'fileLocation', + width: 120, + sorter: true, + render: text => this.bindCodeValue(text, 'file_storage_location'), + }, + { + title: '文件仓库', + dataIndex: 'fileBucket', + width: 200, + ellipsis: { + showTitle: false, + }, + sorter: true, + render: text => {text}, + }, + { + title: '唯一标识id', + dataIndex: 'fileObjectName', + width: 250, + ellipsis: { + showTitle: false, + }, + sorter: true, + render: text => {text}, + }, + { + title: '上传时间', + dataIndex: 'createdTime', + width: 200, + sorter: true, + defaultSortOrder: 'descend', + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ [authName]: 'delete' }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + this.onFileDownload(record)}>下载 + + this.onDelete(record)} + > + 删除 + + + {['png', 'jpeg', 'jpg', 'gif', 'tif', 'bmp'].includes( + record.fileSuffix + ) && this.onFilePreview(record)}>预览} + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData('file_storage_location').then(codes => { + this.setState({ codes }, () => { + onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(modal, record) { + modal.current.open({ + record, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + //#region 自定义方法 + async onFileUpload({ file }) { + this.setState({ uploading: true }) + const table = this.table.current + table.onLoading() + const fd = new FormData() + fd.append('file', file) + try { + await api.sysFileInfoUpload(fd) + table.onReloadData() + } catch { + table.onLoaded() + } finally { + this.setState({ uploading: false }) + } + } + + async onFilePreview({ id }) { + const key = Math.random().toString(16).slice(2) + const hide = Message.loading({ + key, + content: '正在获取文件...', + duration: 0, + }) + const file = await PreviewFileArrayBuffer(id) + if (file) { + const base64 = await ArrayBufferToBase64(file) + var img = new Image() + img.onload = () => { + const items = [ + { + src: base64, + w: img.naturalWidth, + h: img.naturalHeight, + }, + ] + this.photoPreview.current.initPhotoSwipe(items) + + hide() + } + img.onerror = () => { + Message.error({ + key, + content: '获取文件失败', + }) + } + img.src = base64 + } else { + Message.error({ + key, + content: '获取文件失败', + }) + } + } + + async onFileDownload({ id }) { + const key = Math.random().toString(16).slice(2) + const hide = Message.loading({ + key, + content: '正在获取文件...', + duration: 0, + }) + try { + const { data, headers } = await api.sysFileInfoDownload({ id }) + const url = window.URL.createObjectURL(data) + const fileName = GetFileName(headers['content-disposition']) + const a = document.createElement('a') + a.href = url + a.download = fileName + a.click() + window.URL.revokeObjectURL(url) + a.remove() + hide() + } catch { + Message.error({ + key, + content: '下载文件失败', + }) + } + } + //#endregion + + render() { + const { codes, uploading } = this.state + + return ( + +
+ + + + + + + + + + + + + } + operator={ + + this.onFileUpload(e)} fileList={[]}> + + + + } + /> + + + +
+ ) + } +} diff --git a/Web/src/pages/system/log/oplog/index.jsx b/Web/src/pages/system/log/oplog/index.jsx new file mode 100644 index 0000000..6c18cd7 --- /dev/null +++ b/Web/src/pages/system/log/oplog/index.jsx @@ -0,0 +1,274 @@ +import React, { Component } from 'react' +import { + Alert, + Button, + Card, + Descriptions, + Form, + Popconfirm, + Input, + message as Message, + Select, + DatePicker, + Tag, +} from 'antd' +import { Auth, Container, QueryTable } from 'components' +import { api } from 'common/api' +import { toCamelCase } from 'util/format' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import moment from 'moment' +import ReactJson from 'react-json-view' + +const { RangePicker } = DatePicker + +const apiAction = { + page: api.sysOpLogPage, + delete: api.sysOpLogDelete, +} + +const methodColor = { + POST: 'orange', + GET: 'green', +} + +export default class index extends Component { + state = { + codes: { + opType: [], + }, + } + // 表格实例 + table = React.createRef() + // 表格字段 + columns = [ + { + title: '日志名称', + dataIndex: 'name', + width: 200, + sorter: true, + }, + { + title: '请求地址', + dataIndex: 'url', + width: 300, + sorter: true, + render: (text, record) => ( + <> + + {record.reqMethod} + {' '} + {text} + + ), + }, + { + title: '是否成功', + dataIndex: 'success', + width: 100, + render: text => ( + <> + {text ? ( + + ) : ( + + )} + + ), + sorter: true, + }, + { + title: 'ip', + dataIndex: 'ip', + width: 120, + sorter: true, + }, + { + title: '操作时间', + dataIndex: 'opTime', + width: 140, + sorter: true, + defaultSortOrder: 'descend', + }, + { + title: '操作人', + width: 140, + dataIndex: 'account', + sorter: true, + }, + ] + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() {} + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + if (query.dates && query.dates.length) { + query.searchBeginTime = moment(query.dates[0]).format('YYYY-MM-DD HH:mm:ss') + query.searchEndTime = moment(query.dates[1]).format('YYYY-MM-DD HH:mm:ss') + delete query.dates + } + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => +p.code === code) + if (c) { + return c.value + } + } + return null + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + this.table.current.onLoading() + try { + await action + Message.success(successMessage) + this.table.current.onReloadData() + } catch { + this.table.current.onLoaded() + } + } + + onOpLogClear() { + this.onAction(apiAction.delete(), '清空成功') + } + + render() { + return ( + +
+ +
后端bug:任何操作的操作类型都是增加
+
没有记录请求参数.返回结果等信息
+ + } + /> +
+ + + + + + + + + + + + + } + operator={ + + this.onOpLogClear()} + > + + + + } + expandable={{ + expandedRowRender: record => ( + + + {record.methodName} + + + {record.location} + + + {record.browser} + + + {record.os} + + + {record.className} + + + + + + + + + {record.message} + + + ), + }} + /> + +
+ ) + } +} diff --git a/Web/src/pages/system/log/vislog/index.jsx b/Web/src/pages/system/log/vislog/index.jsx new file mode 100644 index 0000000..3f5cafa --- /dev/null +++ b/Web/src/pages/system/log/vislog/index.jsx @@ -0,0 +1,266 @@ +import React, { Component } from 'react' +import { + Alert, + Button, + Card, + Descriptions, + Form, + Popconfirm, + Input, + message as Message, + Select, + DatePicker, +} from 'antd' +import { Auth, Container, QueryTable } from 'components' +import { api } from 'common/api' +import { toCamelCase } from 'util/format' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import moment from 'moment' + +const { RangePicker } = DatePicker + +const apiAction = { + page: api.sysVisLogPage, + delete: api.sysVisLogDelete, +} +export default class index extends Component { + state = { + codes: { + visType: [], + }, + } + // 表格实例 + table = React.createRef() + // 表格字段 + columns = [ + { + title: '日志名称', + dataIndex: 'name', + width: 200, + sorter: true, + }, + { + title: '访问类型', + dataIndex: 'visType', + width: 120, + render: text => <>{this.bindCodeValue(text, 'vis_type')}, + sorter: true, + }, + { + title: '是否成功', + dataIndex: 'success', + width: 120, + render: text => ( + <> + {text ? ( + + ) : ( + + )} + + ), + sorter: true, + }, + { + title: 'ip', + dataIndex: 'ip', + width: 180, + sorter: true, + }, + { + title: '浏览器', + dataIndex: 'browser', + width: 180, + sorter: true, + }, + { + title: '访问时间', + dataIndex: 'visTime', + width: 180, + sorter: true, + defaultSortOrder: 'descend', + }, + { + title: '访问人', + dataIndex: 'account', + width: 180, + sorter: true, + }, + ] + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + this.table.current.onLoading() + getDictData('vis_type').then(res => { + this.setState( + { + codes: res, + }, + () => { + this.table.current.onLoadData() + } + ) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + if (query.dates && query.dates.length) { + query.searchBeginTime = moment(query.dates[0]).format('YYYY-MM-DD HH:mm:ss') + query.searchEndTime = moment(query.dates[1]).format('YYYY-MM-DD HH:mm:ss') + delete query.dates + } + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => +p.code === code) + if (c) { + return c.value + } + } + return null + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + this.table.current.onLoading() + try { + await action + Message.success(successMessage) + this.table.current.onReloadData() + } catch { + this.table.current.onLoaded() + } + } + + onVisLogClear() { + this.onAction(apiAction.delete(), '清空成功') + } + + render() { + return ( + +
+ +
+ + + + + + + + + + + + + + + + } + operator={ + + this.onVisLogClear()} + > + + + + } + expandable={{ + expandedRowRender: record => ( + + + {record.message} + + + ), + }} + /> + +
+ ) + } +} diff --git a/Web/src/pages/system/machine/base.jsx b/Web/src/pages/system/machine/base.jsx new file mode 100644 index 0000000..47403fe --- /dev/null +++ b/Web/src/pages/system/machine/base.jsx @@ -0,0 +1,33 @@ +import React, { Component } from 'react' +import { Card, Col, Descriptions } from 'antd' + +export default class base extends Component { + render() { + const { base } = this.props + + const { hostName, systemOs, wanIp, lanIp, osArchitecture, frameworkDescription } = base + + return ( + <> + + + + {hostName} + {systemOs} + {wanIp} + {lanIp} + {osArchitecture} + + {frameworkDescription} + + + + + + ) + } +} diff --git a/Web/src/pages/system/machine/disk-charts.jsx b/Web/src/pages/system/machine/disk-charts.jsx new file mode 100644 index 0000000..0024161 --- /dev/null +++ b/Web/src/pages/system/machine/disk-charts.jsx @@ -0,0 +1,107 @@ +import React, { Component } from 'react' +import { Card, Col, Row } from 'antd' +import * as echarts from 'echarts' + +export default class diskCharts extends Component { + diskInfo = [] + + diskChart1 = [] + diskChart2 = [] + diskChart3 = [] + diskChart4 = [] + + constructor(props) { + super(props) + + const { base } = props + + this.diskInfo = base.diskInfo + } + + componentDidMount() { + this.diskInfo.forEach(({ size, freeSpace }, i) => { + const dom = this.refs[`disk-chart-${i}`] + this[`diskChart${i}`] = echarts.init(dom) + + const usedSpace = size - freeSpace + const sizeGB = (size / 1024 / 1024 / 1024).toFixed(1) + const usedGB = (usedSpace / 1024 / 1024 / 1024).toFixed(1) + const freeGB = (freeSpace / 1024 / 1024 / 1024).toFixed(1) + + const option = { + tooltip: false, + series: [ + { + name: '磁盘使用量', + type: 'pie', + radius: ['70%', '100%'], + label: { + show: true, + fontSize: '14', + position: 'center', + formatter: `共 ${sizeGB} GB`, + }, + emphasis: { + label: { + show: true, + fontSize: '14', + formatter: '{b}{c}GB({d}%)', + }, + }, + labelLine: { + show: false, + }, + data: [ + { + value: usedGB, + name: '已用', + itemStyle: { + color: usedGB / sizeGB >= 0.85 ? '#ff4d4f' : '#007bff', + }, + }, + { + value: freeGB, + name: '可用', + itemStyle: { color: '#e0e0e0' }, + }, + ], + hoverAnimation: false, + animation: false, + }, + ], + } + this[`diskChart${i}`].setOption(option) + }) + + window.addEventListener('resize', this.onResizeCharts) + } + + componentWillUnmount() { + window.removeEventListener('resize', this.onResizeCharts) + } + + onResizeCharts = () => { + this.diskInfo.forEach((item, i) => { + this[`diskChart${i}`].resize() + }) + } + + render() { + const { diskInfo } = this + + return ( + <> + {diskInfo.map((item, i) => ( + + +
+ {item.description}({item.name}) +
+
+
+ + ))} + + ) + } +} diff --git a/Web/src/pages/system/machine/index.jsx b/Web/src/pages/system/machine/index.jsx new file mode 100644 index 0000000..55155a3 --- /dev/null +++ b/Web/src/pages/system/machine/index.jsx @@ -0,0 +1,46 @@ +import React, { Component } from 'react' +import { Card, Col, Descriptions, Row, Statistic } from 'antd' +import { api } from 'common/api' +import { Container } from 'components' +import { isEqual } from 'lodash' +import moment from 'moment' + +import Base from './base' +import UseCharts from './use-charts' +import DiskCharts from './disk-charts' + +export default class index extends Component { + state = { + loading: true, + base: {}, + network: {}, + } + + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) || this.props.paneActived !== props.paneActived + } + + async componentDidMount() { + try { + const { data: base } = await api.sysMachineBase() + this.setState({ loading: false, base }) + } catch {} + } + + render() { + const { paneActived } = this.props + + const { loading } = this.state + + return ( + +
+ + {!loading && } + {!loading && } + {!loading && } + +
+ ) + } +} diff --git a/Web/src/pages/system/machine/use-charts.jsx b/Web/src/pages/system/machine/use-charts.jsx new file mode 100644 index 0000000..22f903b --- /dev/null +++ b/Web/src/pages/system/machine/use-charts.jsx @@ -0,0 +1,304 @@ +import React, { Component } from 'react' +import { Card, Col, Descriptions, Row, Statistic } from 'antd' +import * as echarts from 'echarts' +import moment from 'moment' +import { api } from 'common/api' + +export default class useCharts extends Component { + state = { + use: {}, + + nowMoment: moment(), + } + + timer = null + timerMoment = null + + actived = true + + systemStart = moment() + + now = Date.now() + + cpuChart = null + cpuData = [] + + ramChart = null + ramData = [] + + shouldComponentUpdate(props) { + // 当前页签未选中时停止获取状态 + if (this.props.actived !== props.actived) { + this.actived = props.actived + if (props.actived) { + this.start() + } else { + this.stop() + } + } + return true + } + + componentDidMount() { + this.systemStart = moment().add(-this.props.base.runTime) + this.initCpuChart() + this.initRamChart() + + this.start() + + window.addEventListener('resize', this.onResizeCharts) + } + + componentWillUnmount() { + this.stop() + + window.removeEventListener('resize', this.onResizeCharts) + } + + start() { + this.actived = true + this.refreshData() + this.timerMoment = setInterval(() => { + this.setState({ nowMoment: moment() }) + }, 1000) + } + + stop() { + this.actived = false + clearTimeout(this.timer) + clearInterval(this.timerMoment) + } + + async refreshData() { + const { data: use } = await api.sysMachineUse() + + this.now = Date.now() + + this.cpuData.shift() + this.cpuData.push({ + name: this.now, + value: [this.now, use.cpuRate], + }) + this.cpuChart.setOption({ + series: [{ data: this.cpuData }], + }) + + this.ramData.shift() + this.ramData.push({ + name: this.now, + value: [this.now, use.ramRate], + }) + this.ramChart.setOption({ + series: [{ data: this.ramData }], + }) + + this.setState({ use }) + + if (this.actived) + this.timer = setTimeout(() => { + this.refreshData() + }, 3000) + } + + initCpuChart() { + for (let i = 0; i < 60; i++) { + const past = this.now - (60 - i) * 1000 + this.cpuData.push({ + name: past, + value: [past, -1], + }) + } + + const dom = this.refs['cpu-chart'] + this.cpuChart = echarts.init(dom) + const option = { + grid: { + show: true, + top: 0, + left: 0, + right: 0, + bottom: 0, + borderColor: 'rgba(0, 123, 255, 1)', + borderWidth: 2, + zlevel: 2, + }, + tooltip: false, + xAxis: { + type: 'time', + axisTick: { + show: false, + }, + axisLabel: { + show: false, + }, + axisLine: { + show: false, + }, + }, + yAxis: { + type: 'value', + max: 100, + min: 0, + axisLabel: { + show: false, + }, + }, + series: [ + { + type: 'line', + showSymbol: false, + hoverAnimation: false, + animation: false, + data: this.cpuData, + lineStyle: { + width: 1, + color: 'rgba(0, 123, 255, .8)', + }, + areaStyle: { + color: 'rgba(0, 123, 255, .3)', + }, + }, + ], + } + this.cpuChart.setOption(option) + } + + initRamChart() { + for (let i = 0; i < 60; i++) { + const past = this.now - (60 - i) * 1000 + this.ramData.push({ + name: past, + value: [past, -1], + }) + } + + const dom = this.refs['ram-chart'] + this.ramChart = echarts.init(dom) + const option = { + grid: { + show: true, + top: 0, + left: 0, + right: 0, + bottom: 0, + borderColor: 'rgba(83, 29, 171, 1)', + borderWidth: 2, + zlevel: 2, + }, + tooltip: false, + xAxis: { + type: 'time', + axisTick: { + show: false, + }, + axisLabel: { + show: false, + }, + axisLine: { + show: false, + }, + }, + yAxis: { + type: 'value', + max: 100, + min: 0, + axisLabel: { + show: false, + }, + }, + series: [ + { + type: 'line', + showSymbol: false, + hoverAnimation: false, + animation: false, + data: this.ramData, + lineStyle: { + width: 1, + color: 'rgba(83, 29, 171, .8)', + }, + areaStyle: { + color: 'rgba(83, 29, 171, .3)', + }, + }, + ], + } + this.ramChart.setOption(option) + } + + onResizeCharts = () => { + this.cpuChart.resize() + this.ramChart.resize() + } + + render() { + const { base } = this.props + const { use, nowMoment } = this.state + const { cpuName, cpuBaseSpeed, processorCount, totalRam } = base + const { cpuRate, ramRate } = use + + const diffDays = nowMoment.diff(this.systemStart, 'days') + const diff = + diffDays + ':' + moment(nowMoment.diff(this.systemStart)).utc().format('HH:mm:ss') + + return ( + <> + + + +
CPU
+
{cpuName}
+
+
+ +
+ + + + + + + {((cpuBaseSpeed || 0) / 1000).toFixed(2)} GHz + + + {processorCount || 0} + + + + + + + + + +
内存
+
{((totalRam || 0) / 1024).toFixed(1)} GB
+
+
+ +
+ + + + + + + + ) + } +} diff --git a/Web/src/pages/system/menu/form.jsx b/Web/src/pages/system/menu/form.jsx new file mode 100644 index 0000000..88dfd54 --- /dev/null +++ b/Web/src/pages/system/menu/form.jsx @@ -0,0 +1,416 @@ +import React, { Component } from 'react' +import { Form, Input, InputNumber, Radio, Select, Spin, Switch, TreeSelect } from 'antd' +import { AntIcon, IconSelector } from 'components' +import { cloneDeep } from 'lodash' +import getDictData from 'util/dic' +import { api } from 'common/api' +import { EMPTY_ID } from 'util/global' + +const initialValues = { + type: 1, + openType: 1, + weight: '2', + visible: true, + sort: 100, +} + +export default class form extends Component { + state = { + // 加载状态 + loading: true, + codes: { + menuType: [], + openType: [], + menuWeight: [], + }, + options: { + appList: [], + parentTreeData: [], + }, + + addType: [], + type: initialValues.type, + openType: initialValues.openType, + icon: '', + } + + // 表单实例 + form = React.createRef() + + iconSelector = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + const form = this.form.current + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + const codes = await getDictData('menu_type', 'open_type', 'menu_weight') + const appList = await this.onLoadSysApplist() + let parentTreeData = [] + if (params.isParent) { + parentTreeData = await this.onLoadMenuTree(params.parent.application) + } + + if (params.record) { + parentTreeData = await this.onLoadMenuTree(params.record.application) + } else { + this.setState({ addType: params.addType }) + if (params.addType.length) { + form.setFieldsValue({ + type: params.addType[0], + }) + } + } + const icon = params.record && params.record.icon + this.setState({ + codes, + options: { + appList, + parentTreeData, + }, + icon, + }) + //#endregion + if (params.isParent) { + form.setFieldsValue({ + pid: params.parent.id, + application: params.parent.application, + }) + } else { + form.setFieldsValue(this.record) + } + + this.setState({ loading: false }) + + this.onTypeChange() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + // 目录的pic必须为空 + if (!postData.type) { + postData.pid = EMPTY_ID + } + //#endregion + return postData + } + } + + //#region 自定义方法 + async onLoadSysApplist() { + const { data } = await api.getAppList() + return data + } + + async onLoadMenuTree(application) { + const { data } = await api.getMenuTree({ application }) + return [ + { + id: EMPTY_ID, + parentId: undefined, + title: '顶级', + value: EMPTY_ID, + pid: undefined, + children: data, + }, + ] + } + + onTypeChange() { + this.onTypeChangeGroup() + // const form = this.form.current + // const { type } = form.getFieldsValue() + // if ([0, 2].includes(type)) { + // form.setFieldsValue({ + // openType: 0, + // }) + // } else { + // form.setFieldsValue({ + // openType: 1, + // }) + // } + } + + onOpenTypeChange() { + this.onTypeChangeGroup() + } + + onTypeChangeGroup() { + const form = this.form.current + const { type, openType } = form.getFieldsValue() + // if (type == 1 && openType == 2) { + // form.setFieldsValue({ + // component: 'iframe' + // }) + // } else { + // form.setFieldsValue({ + // component: '' + // }) + // } + + this.setState({ + type, + openType, + }) + } + + async onApplicationChange(value) { + this.setState({ + loading: true, + }) + const parentTreeData = await this.onLoadMenuTree(value) + this.setState({ + loading: false, + options: { + ...this.state.options, + parentTreeData, + }, + }) + this.form.current.setFieldsValue({ + pid: undefined, + }) + } + + onSelectIcon(icon) { + this.form.current.setFieldsValue({ + icon, + }) + this.setState({ icon }) + } + //#endregion + + render() { + const { loading, codes, options, addType, type, openType, icon } = this.state + + return ( +
+ }> +
+

基本信息

+
+ + 目录:一级菜单,默认添加在顶级 +
+ 菜单:二级菜单 +
+ 按钮:菜单中对应到接口的功能 + + } + rules={[{ required: true, message: '请选择菜单类型' }]} + > + this.onTypeChange(e)}> + {codes.menuType.map(item => { + return ( + + {item.value} + + ) + })} + +
+ + + + + + + + + + {type != 0 && ( + + + + )} + + 系统权重:菜单/功能任何角色可用 +
+ 业务权重:菜单/功能为超级管理员不可用,可防止管理员误操作 + + } + rules={[{ required: true, message: '请选择权重' }]} + > + + {codes.menuWeight.map(item => { + return ( + + {item.value} + + ) + })} + +
+
+

扩展信息

+
+ {type == 1 && ( + + this.onOpenTypeChange(e)}> + {codes.openType.map(item => { + return ( + + {item.value} + + ) + })} + + + )} + {type == 1 && openType == 1 && ( + + + + )} + {type == 1 && openType == 2 && ( + + + + )} + {type == 1 && openType == 3 && ( + + + + )} + {type == 2 && ( + + + + )} + {type == 2 && ( + + + + )} + + + + {type != 2 && ( + + } + addonAfter={ + + this.iconSelector.current.open( + this.form.current.getFieldValue('icon') + ) + } + /> + } + /> + + )} + + + + + + +
+
+
+ + this.onSelectIcon(icon)} /> + + ) + } +} diff --git a/Web/src/pages/system/menu/index.jsx b/Web/src/pages/system/menu/index.jsx new file mode 100644 index 0000000..a7566f3 --- /dev/null +++ b/Web/src/pages/system/menu/index.jsx @@ -0,0 +1,415 @@ +import React, { Component } from 'react' +import { Button, Table, Card, Popconfirm, message as Message, Row, Col, Tooltip, Tag } from 'antd' +import { isEqual } from 'lodash' +import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import getDictData from 'util/dic' +import auth from 'components/authorized/handler' +import { toCamelCase } from 'util/format' +import FormBody from './form' + +// 配置页面所需接口函数 +const apiAction = { + page: api.getMenuList, + add: api.sysMenuAdd, + edit: api.sysMenuEdit, + delete: api.sysMenuDelete, +} + +// 用于弹窗标题 +const name = '菜单' + +export default class index extends Component { + state = { + codes: { + menuType: [], + menuWeight: [], + openType: [], + }, + } + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + // 表格字段 + columns = [ + { + title: '菜单名称', + width: 220, + dataIndex: 'name', + }, + { + title: '菜单类型', + width: 100, + dataIndex: 'type', + render: text => this.bindCodeValue(text, 'menu_type'), + }, + { + title: '图标', + width: 100, + dataIndex: 'icon', + render: text => text && , + }, + { + title: '连接', + width: 220, + dataIndex: 'openType', + render: (text, record) => { + switch (text) { + case 1: + return ( + <> + {this.bindCodeValue(text, 'open_type')}{' '} + {record.component} + + ) + case 2: + return ( + <> + {this.bindCodeValue(text, 'open_type')}{' '} + {record.link} + + ) + case 3: + return ( + <> + {this.bindCodeValue(text, 'open_type')}{' '} + {record.redirect} + + ) + default: + return '' + } + }, + }, + { + title: '排序', + width: 100, + dataIndex: 'sort', + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ sysApp: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 220, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen({ modal: this.editForm, record })}> + 编辑 + + + + this.onDelete(record)} + > + 删除 + + + {record.type < 2 && ( + + + this.onOpen({ + modal: this.addForm, + record, + isParent: true, + addType: record.type == 0 ? [1] : [2], + }) + } + > + {record.type == 0 ? '新增子菜单' : '新增功能'} + + + )} + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + this.table.current.onLoading() + getDictData('menu_type', 'menu_weight', 'open_type').then(res => { + this.setState( + { + codes: res, + }, + () => { + this.table.current.onLoadData() + } + ) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen({ modal, record, isParent = false, addType = [] }) { + const params = isParent + ? { + parent: record, + isParent, + addType, + } + : { + record, + isParent, + addType, + } + + modal.current.open(params) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + this.table.current.onLoading() + try { + await action + Message.success(successMessage) + this.table.current.onReloadData() + } catch { + this.table.current.onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + //#region 自定义方法 + /** + * 绘制新的扩展行 + * @param {*} record + */ + onRowRender(record) { + const grids = [] + let isFunction = false + record.children.map(item => { + if (!(item.children && item.children.length) && item.type == 2) { + isFunction = true + const grid = ( + +

+ {item.visibleParent && ( + + + + )} + {item.name} +

+ {item.permission} +
+ + + + + this.onOpen({ modal: this.editForm, record: item }) + } + > + + + + + + this.onDelete(item)} + > + + + + + + + + +
+
+ ) + grids.push(grid) + } else if ( + (item.children && item.children.length) || + (!(item.children && item.children.length) && item.type == 1) + ) { + grids.push(item) + } + }) + if (isFunction) { + grids.push( + + this.onOpen({ modal: this.addForm, record, isParent: true, addType: [2] }) + } + > +
+ +
+ 新增功能 +
+ ) + return {grids} + } else { + return ( +
record.id} + expandable={{ + expandedRowRender: record => this.onRowRender(record), + rowExpandable: record => record.children && record.children.length, + }} + childrenColumnName="none" + bordered={true} + pagination={false} + /> + ) + } + } + //#endregion + + render() { + return ( + +
+ + this.onRowRender(record), + rowExpandable: record => record.children && record.children.length, + }} + operator={ + + + + } + /> + + + this.table.current.onReloadData()} + > + + + + this.table.current.onReloadData()} + > + + +
+ ) + } +} diff --git a/Web/src/pages/system/notice/form.jsx b/Web/src/pages/system/notice/form.jsx new file mode 100644 index 0000000..2f12894 --- /dev/null +++ b/Web/src/pages/system/notice/form.jsx @@ -0,0 +1,251 @@ +import React, { Component } from 'react' +import { Form, Spin, Input, Radio, Select, Upload, Button } from 'antd' +import { api } from 'common/api' +import { AntIcon, BraftEditor } from 'components' +import getDictData from 'util/dic' +import { BlobToBase64, GetFileName, PreviewFile } from 'util/file' +import { cloneDeep } from 'lodash' + +const initialValues = {} + +export default class form extends Component { + state = { + // 加载状态 + loading: true, + options: { + userList: [], + }, + codes: { + noticeType: [], + noticeStatus: [], + }, + funloader: Object, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式,也可以在此处调用获取详细数据接口 + if (params.id) { + this.record = (await api.sysNoticeDetail({ id: params.id })).data + } + const { + data: { items: userList }, + } = await this.onLoadUser() + const codes = await getDictData('notice_status', 'notice_type') + + if (this.record) { + const { attachments } = this.record + if (attachments) { + const fileValue = [] + const fileList = attachments.split(',') + for (const fileId of fileList) { + try { + const file = await PreviewFile(fileId) + const base64 = await BlobToBase64(file) + fileValue.push({ + uid: fileId, + response: fileId, + name: file.name, + url: base64, + status: 'done', + }) + } catch { + const { data: file } = await api.sysFileInfoDetail({ id: fileId }) + fileValue.push({ + uid: fileId, + response: '文件已丢失', + name: file.fileOriginName, + status: 'error', + }) + } + } + this.record.attachments = fileValue + } + } + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false, + options: { + userList, + }, + codes, + }) + //加载 BraftEditor 富文本插件 + this.state.funloader() + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + postData.status = 0 + } else { + postData.status = 1 + } + //#region 从前段转换后端所需格式 + postData.attachments = postData.attachments.map(item => + item.uid.startsWith('rc-upload') ? item.response : item.uid + ) + //#endregion + return postData + } + } + + //#region 自定义方法 + /** + * 接受子组件传过来的方法 + * 等页面加载完毕后调用 + */ + getChildrenMsg = funLoad => { + this.setState({ + funloader: funLoad, + }) + } + async onLoadUser() { + const data = await api.getUserPage() + return data + } + async onFileUpload({ file, onProgress, onSuccess, onError }) { + onProgress({ + percent: 0, + }) + const fd = new FormData() + fd.append('file', file) + try { + const { data: fileId } = await api.sysFileInfoUpload(fd) + onSuccess(fileId) + } catch { + onError() + } + } + async onFileDownload(file) { + const { data, headers } = await api.sysFileInfoDownload({ id: file.response }) + const url = window.URL.createObjectURL(data) + const fileName = GetFileName(headers['content-disposition']) + const a = document.createElement('a') + a.href = url + a.download = fileName + a.click() + window.URL.revokeObjectURL(url) + a.remove() + } + //#endregion + + render() { + const { options, codes } = this.state + + return ( + + }> +
+ + + + + + {codes.noticeType.map(item => ( + + {item.value} + + ))} + + + { + const v = value.replace(/<\/?.+?\/?>/g, '') + if (!v) { + throw Error('请输入内容') + } + }, + }, + ]} + > + + + { + if (Array.isArray(e)) { + return e + } + return e && e.fileList + }} + > + this.onFileUpload(e)} + showUploadList={{ + showRemoveIcon: true, + showDownloadIcon: true, + }} + onPreview={() => false} + onDownload={file => this.onFileDownload(file)} + > + + + + + + +
+
+ + ) + } +} diff --git a/Web/src/pages/system/notice/index.jsx b/Web/src/pages/system/notice/index.jsx new file mode 100644 index 0000000..26cfa4a --- /dev/null +++ b/Web/src/pages/system/notice/index.jsx @@ -0,0 +1,371 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, message as Message, Popconfirm, Select } from 'antd' +import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import FormBody from './form' + +/** + * 注释段[\/**\/]为必须要改 + */ + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api.sysNoticePage, + add: api.sysNoticeAdd, + edit: api.sysNoticeEdit, + delete: api.sysNoticeDelete, + status: api.sysNoticeChangeStatus, +} + +/** + * 用于弹窗标题 + * [必要] + */ +const name = '通知公告' + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = 'sysNotice' + +export default class index extends Component { + state = { + codes: { + noticeStatus: [], + noticeType: [], + }, + } + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + columns = [ + { + title: '标题', + dataIndex: 'title', + width: 300, + sorter: true, + }, + { + title: '发布人', + dataIndex: 'publicUserName', + width: 120, + sorter: true, + }, + { + title: '发布时间', + dataIndex: 'createdTime', + width: 150, + sorter: true, + }, + { + title: '发布单位', + dataIndex: 'publicOrgName', + width: 150, + sorter: true, + }, + { + title: '类型', + dataIndex: 'type', + width: 120, + sorter: true, + render: text => this.bindCodeValue(text, 'notice_type'), + }, + { + title: '状态', + dataIndex: 'status', + width: 120, + render: text => this.bindCodeValue(text, 'notice_status'), + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ [authName]: [['edit'], ['changeStatus']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 200, + dataIndex: 'actions', + render: (text, record) => ( + + {record.status === 1 ? ( + + this.onGoBack(record.id)} + > + 撤回 + + + ) : ( + [ + + this.onOpen(this.editForm, record.id)}> + 编辑 + + , + + this.onPublish(record.id)} + > + 发布 + + , + + this.onDelete(record.id)} + > + 删除 + + , + ] + )} + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData('notice_status', 'notice_type').then(codes => { + this.setState({ codes }, () => { + onLoadData() + }) + }) + } + + subUniqueKey(text, index) { + return text.substr(index, 5) + } + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id) { + modal.current.open({ + id, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + /** + * 删除 + * @param {*} id + */ + onDelete(id) { + this.onAction(apiAction.status({ id, status: 3 }), '删除成功') + } + /** + * 发布 + * @param {*} id + */ + onPublish(id) { + this.onAction(apiAction.status({ id, status: 1 }), '发布成功') + } + /** + * 撤回 + * @param {*} id + */ + onGoBack(id) { + this.onAction(apiAction.status({ id, status: 2 }), '撤回成功') + } // + + render() { + const { codes } = this.state + return ( + +
+ + + + + + + + + + } + operator={ + record.id} auth={{ [authName]: 'add' }}> + + + } + /> + + + + ( + + ), + }, + ]} + ref={this.addForm} + onSuccess={() => this.table.current.onReloadData()} + > + + + + + + this.table.current.onReloadData()} + > + + + +
+ ) + } +} diff --git a/Web/src/pages/system/noticeReceived/index.jsx b/Web/src/pages/system/noticeReceived/index.jsx new file mode 100644 index 0000000..3f99595 --- /dev/null +++ b/Web/src/pages/system/noticeReceived/index.jsx @@ -0,0 +1,218 @@ +import React, { Component } from 'react' +import { Card, Form, Input, Select } from 'antd' +import { Auth, Container, NoticeDetail, QueryTable } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import { getSearchInfo, QueryType } from 'util/query' + +/** + * 统一配置权限标识 + * [必要] + */ +const authName = 'sysNotice' + +export default class index extends Component { + state = { + codes: { + noticeStatus: [], + noticeType: [], + readStatus: [], + }, + } + + // 表格实例 + table = React.createRef() + + detail = React.createRef() + + columns = [ + { + title: '标题', + dataIndex: 'title', + width: 300, + sorter: true, + }, + { + title: '发布人', + dataIndex: 'publicUserName', + width: 150, + sorter: true, + }, + { + title: '发布时间', + dataIndex: 'createdTime', + width: 200, + sorter: true, + }, + { + title: '发布单位', + dataIndex: 'publicOrgName', + width: 200, + sorter: true, + }, + { + title: '类型', + dataIndex: 'type', + width: 120, + render: text => this.bindCodeValue(text, 'notice_type'), + }, + { + title: '已读未读', + dataIndex: 'readStatus', + width: 120, + render: text => this.bindCodeValue(text, 'read_status'), + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + const flag = auth({ [authName]: 'received' }) + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + this.detail.current.onOpenDetail(record.id)}>查看 + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + getDictData('notice_status', 'notice_type', 'read_status').then(codes => { + this.setState({ codes }, () => { + onLoadData() + }) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const searchInfo = getSearchInfo({ + query, + queryType: { + type: QueryType.Equal, + title: QueryType.Like, + readStatus: QueryType.Equal, + }, + }) + + const { data } = await api.sysNoticeReceived({ + ...params, + searchInfo, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id) { + modal.current.onOpenDetail(id) + } + + render() { + const { codes } = this.state + return ( + +
+ + + + + + + + + + + + + } + /> + + +
+ ) + } +} diff --git a/Web/src/pages/system/org/form.jsx b/Web/src/pages/system/org/form.jsx new file mode 100644 index 0000000..627bdec --- /dev/null +++ b/Web/src/pages/system/org/form.jsx @@ -0,0 +1,227 @@ +import React, { Component } from 'react' +import { Cascader, Form, Input, InputNumber, Select, Spin, TreeSelect } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import getDictData from 'util/dic' +import { EMPTY_ID } from 'util/global' +import { api } from 'common/api' + +const initialValues = { + sort: 100 +} + +export default class form extends Component { + + state = { + // 加载状态 + loading: true, + + codes: { + orgType: [] + }, + + options: { + orgData: [], + areaData: [] + } + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + const orgData = await this.loadOrgData() + const areaData = await this.loadAreaData() + + const codes = await getDictData('org_type') + this.setState({ + codes, + options: { + orgData, + areaData + } + }) + + const areaCode = []; + const findCode = (data, level) => { + level = level || 0; + for (let i = 0; i < data.length; i++) { + const item = data[i]; + areaCode[level] = item.code; + + if (item.code === params.record.areaCode) { + areaCode.length = level + 1; + return true; + } + + if (item.children && item.children.length) { + const found = findCode(item.children, level + 1); + if (found) { + return true; + } + } + } + }; + + if (params.record && params.record.areaCode) { + findCode(areaData); + } + + this.record = { + pid: params.orgId, + ...this.record, + areaCode + } + this.record.areaCode = areaCode + //#endregion + + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + postData.areaCode = postData.areaCode[postData.areaCode.length - 1] + //#endregion + return postData + } + } + + //#region 自定义方法 + async loadOrgData() { + const { data } = await api.getOrgTree() + return [{ + id: EMPTY_ID, + parentId: undefined, + title: '顶级', + value: EMPTY_ID, + pid: undefined, + children: data, + }] + } + + async loadAreaData() { + const { data } = await api.getAreaTree() + const clearChiildren = (data) => { + data.forEach((item) => { + if (item.children && item.children.length) { + clearChiildren(item.children); + } else { + delete item.children; + } + }); + }; + clearChiildren(data); + return data + } + + onAreaCodeChange(selectedOptions) { + const data = selectedOptions[selectedOptions.length - 1] + this.form.current.setFieldsValue({ + name: data.name, + code: data.code + }) + } + //#endregion + + render() { + return ( + + }> +
+ + this.onAreaCodeChange(selectedOptions)} + /> + + + + + + + + + + + + + + + + + + + +
+
+ + ) + } +} diff --git a/Web/src/pages/system/org/index.jsx b/Web/src/pages/system/org/index.jsx new file mode 100644 index 0000000..76ac8f1 --- /dev/null +++ b/Web/src/pages/system/org/index.jsx @@ -0,0 +1,321 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, message as Message, Popconfirm, Select } from 'antd' +import { + AntIcon, + Auth, + Container, + ModalForm, + QueryTable, + QueryTableActions, + QueryTreeLayout, +} from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { toCamelCase } from 'util/format' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import FormBody from './form' + +const apiAction = { + tree: api.getOrgTree, + page: api.getOrgPage, + add: api.sysOrgAdd, + edit: api.sysOrgEdit, + delete: api.sysOrgDelete, +} + +const name = '机构' + +export default class index extends Component { + state = { + codes: { + orgType: [], + }, + } + + // 树框架实例 + treeLayout = React.createRef() + + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + // 树选中节点 + selectId = undefined + + // 表格字段 + columns = [ + { + title: '机构名称', + width: 400, + dataIndex: 'name', + sorter: true, + }, + { + title: '唯一编码', + width: 200, + dataIndex: 'code', + sorter: true, + }, + { + title: '机构类型', + dataIndex: 'type', + width: 120, + sorter: true, + render: text => <>{this.bindCodeValue(text, 'org_type')}, + }, + { + title: '排序', + width: 80, + dataIndex: 'sort', + sorter: true, + defaultSortOrder: 'ascend', + }, + { + title: '备注', + dataIndex: 'remark', + width: 400, + sorter: true, + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ sysOrg: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(this.editForm, record)}>编辑 + + + this.onDelete(record)} + > + 删除 + + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + this.table.current.onLoading() + getDictData('org_type').then(res => { + this.setState( + { + codes: res, + }, + () => { + this.table.current.onLoadData() + } + ) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + query = { + ...query, + pid: this.selectId, + } + if (params) { + params.sortField = 'areaCode' + params.sortOrder = 'ascend' + } + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 调用树结构数据接口 + * [异步,必要] + * @returns + */ + loadTreeData = async () => { + const { data } = await apiAction.tree() + return data + } + + /** + * 树节点选中事件 + * [必要] + * @param {*} id + */ + onSelectTree(id) { + this.selectId = id + this.table.current.onReloadData() + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(modal, record) { + modal.current.open({ + orgId: this.selectId, + record, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + this.table.current.onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + this.treeLayout.current.onReloadData() + this.table.current.onReloadData() + } catch { + this.table.current.onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + //#region 自定义方法 + //#endregion + + render() { + return ( + this.onSelectTree(key)} + > + + + + + + + + + + + } + operator={ + + } + /> + + + + this.onAction()} + > + + + + this.onAction()} + > + + + + ) + } +} diff --git a/Web/src/pages/system/pos/form.jsx b/Web/src/pages/system/pos/form.jsx new file mode 100644 index 0000000..ac8ded9 --- /dev/null +++ b/Web/src/pages/system/pos/form.jsx @@ -0,0 +1,101 @@ +import React, { Component } from 'react' +import { Form, Input, InputNumber, Spin } from 'antd' +import { AntIcon, IconSelector } from 'components' +import { cloneDeep } from 'lodash' + +const initialValues = { + sort: 100 +} + +export default class form extends Component { + state = { + // 加载状态 + loading: true, + } + + // 表单实例 + form = React.createRef() + + iconSelector = React.createRef() + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + render() { + return ( + + }> +
+ + + + + + + + + + + + + +
+
+ + ) + } +} \ No newline at end of file diff --git a/Web/src/pages/system/pos/index.jsx b/Web/src/pages/system/pos/index.jsx new file mode 100644 index 0000000..e59dda6 --- /dev/null +++ b/Web/src/pages/system/pos/index.jsx @@ -0,0 +1,203 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, Popconfirm, message as Message } from 'antd' +import { isEqual } from 'lodash' +import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import FormBody from './form' + +// 配置页面所需接口函数 +const apiAction = { + page: api.sysPosPage, + add: api.sysPosAdd, + edit: api.sysPosEdit, + delete: api.sysPosDelete, +} + +// 用于弹窗标题 +const name = '职位' + +export default class index extends Component { + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + columns = [ + { + title: '职位名称', + dataIndex: 'name', + width: 400, + sorter: true, + }, + { + title: '唯一编码', + dataIndex: 'code', + width: 400, + sorter: true, + }, + { + title: '排序', + dataIndex: 'sort', + width: 80, + sorter: true, + defaultSortOrder: 'ascend', + }, + { + title: '备注', + dataIndex: 'remark', + width: 400, + sorter: true, + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ sysPos: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(this.editForm, record)}>编辑 + + + this.onDelete(record)} + > + 删除 + + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(modal, record) { + modal.current.open({ + record, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + this.table.current.onLoading() + try { + await action + Message.success(successMessage) + this.table.current.onReloadData() + } catch { + this.table.current.onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + render() { + return ( + +
+ + + + + + + + + + } + operator={ + + } + > + + this.table.current.onReloadData()} + > + + + + this.table.current.onReloadData()} + > + + +
+ ) + } +} diff --git a/Web/src/pages/system/role/data.jsx b/Web/src/pages/system/role/data.jsx new file mode 100644 index 0000000..d6c08ec --- /dev/null +++ b/Web/src/pages/system/role/data.jsx @@ -0,0 +1,166 @@ +import React, { Component } from 'react' +import { Form, Select, Spin, TreeSelect } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import { api } from 'common/api' +import getDictData from 'util/dic' + +const { SHOW_PARENT } = TreeSelect + +const initialValues = {} + +export default class data extends Component { + state = { + // 加载状态 + loading: true, + dataScopeType: [], + orgTreeData: [], + arerTreeData: [], + orgCheckedKeys: [], + + isDefine: false, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + const { dataScopeType } = await getDictData('data_scope_type') + const orgTreeData = await this.onLoadOrgTreeData() + const arerTreeData = await this.onLoadAreaTreeData() + const orgCheckedKeys = await this.onLoadRoleOwn(this.record.id) + this.setState({ + dataScopeType, + orgTreeData, + arerTreeData, + orgCheckedKeys, + }) + //#endregion + this.form.current.setFieldsValue({ + dataScopeType: this.record.dataScopeType.toString(), + }) + + this.onChange(this.record.dataScopeType) + + this.setState({ + loading: false, + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + async onLoadOrgTreeData() { + const { data } = await api.getOrgTree() + return data + } + + async onLoadAreaTreeData() { + const { data } = await api.getAreaTree() + return data + } + + async onLoadRoleOwn(id) { + const { data } = await api.sysRoleOwnData({ id }) + return data + } + + onChange(value) { + if (value == 5) { + this.setState({ + isDefine: true, + }) + } else { + this.setState({ + isDefine: false, + }) + } + } + //#endregion + + render() { + return ( + + }> +
+ + + + {this.state.isDefine && ( + <> + + + + + + + + )} +
+
+ + ) + } +} diff --git a/Web/src/pages/system/role/form.jsx b/Web/src/pages/system/role/form.jsx new file mode 100644 index 0000000..594deee --- /dev/null +++ b/Web/src/pages/system/role/form.jsx @@ -0,0 +1,104 @@ +import React, { Component } from 'react' +import { Form, Input, InputNumber, Spin } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' + +const initialValues = { + sort: 100 +} + +export default class form extends Component { + + state = { + // 加载状态 + loading: true, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + //#endregion + + render() { + return ( + + }> +
+ + + + + + + + + + + + +
+
+ + ) + } +} diff --git a/Web/src/pages/system/role/index.jsx b/Web/src/pages/system/role/index.jsx new file mode 100644 index 0000000..865db78 --- /dev/null +++ b/Web/src/pages/system/role/index.jsx @@ -0,0 +1,266 @@ +import React, { Component } from 'react' +import { Button, Card, Dropdown, Form, Input, Menu, message as Message, Popconfirm } from 'antd' +import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import FormBody from './form' +import MenuForm from './menu' +import DataForm from './data' + +// 配置页面所需接口函数 +const apiAction = { + page: api.getRolePage, + add: api.sysRoleAdd, + edit: api.sysRoleEdit, + delete: api.sysRoleDelete, + + grantMenu: api.sysRoleGrantMenu, + grantData: api.sysRoleGrantData, +} + +// 用于弹窗标题 +const name = '角色' + +export default class index extends Component { + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + menuForm = React.createRef() + dataForm = React.createRef() + + columns = [ + { + title: '角色名', + dataIndex: 'name', + width: 400, + sorter: true, + }, + { + title: '唯一编码', + dataIndex: 'code', + width: 400, + sorter: true, + }, + { + title: '排序', + dataIndex: 'sort', + width: 80, + sorter: true, + defaultSortOrder: 'ascend', + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ sysRole: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, record) => ( + + + this.onOpen(this.editForm, record)}>编辑 + + + this.onDelete(record)} + > + 删除 + + + + + + + + this.onOpen(this.menuForm, record) + } + > + 授权菜单 + + + + + + + this.onOpen(this.dataForm, record) + } + > + 授权数据 + + + + + } + > + + 授权 + + + + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} record + */ + onOpen(modal, record) { + modal.current.open({ + record, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + this.table.current.onLoading() + try { + await action + Message.success(successMessage) + this.table.current.onReloadData() + } catch { + this.table.current.onLoaded() + } + } + + /** + * 删除 + * @param {*} record + */ + onDelete(record) { + this.onAction(apiAction.delete(record), '删除成功') + } + + //#region 自定义方法 + //#endregion + + render() { + return ( + +
+ + + + + + + + + + } + operator={ + + + + } + /> + + + this.table.current.onReloadData()} + > + + + + this.table.current.onReloadData()} + > + + + + this.table.current.onReloadData()} + > + + + + this.table.current.onReloadData()} + > + + +
+ ) + } +} diff --git a/Web/src/pages/system/role/menu.jsx b/Web/src/pages/system/role/menu.jsx new file mode 100644 index 0000000..6c3eb76 --- /dev/null +++ b/Web/src/pages/system/role/menu.jsx @@ -0,0 +1,89 @@ +import React, { Component } from 'react' +import { cloneDeep } from 'lodash' +import { AntIcon, AuthorityView } from 'components' +import { api } from 'common/api' +import { Empty, Spin } from 'antd' + +export default class form extends Component { + + state = { + // 加载状态 + loading: true, + defaultSelectedKeys: [] + } + + selectedKeys = [] + + view = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + const { data } = await api.sysRoleOwnMenu({ id: this.record.id }) + this.setState({ + defaultSelectedKeys: data + }) + this.view.current.onLoadData() + //#endregion + + this.setState({ + loading: false + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const postData = {} + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + postData.grantMenuIdList = this.selectedKeys + //#endregion + return postData + } + + //#region 自定义方法 + async loadData() { + const { data } = await api.SysMenuTreeForGrant() + return data + } + //#endregion + + render() { + return ( + }> + this.selectedKeys = s3} + ref={this.view} + /> + { this.state.loading && } + + ) + } +} diff --git a/Web/src/pages/system/timers/form.jsx b/Web/src/pages/system/timers/form.jsx new file mode 100644 index 0000000..1c567ea --- /dev/null +++ b/Web/src/pages/system/timers/form.jsx @@ -0,0 +1,243 @@ +import React, { Component } from 'react' +import { Row, Col, Form, Input, Spin, Radio, Switch } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import { api } from 'common/api' + +const initialValues = { requestType: 2, startNow: 0, doOnce: 1, timerType: 1, executeType: 1 } + +export default class form extends Component { + state = { + // 加载状态 + loading: true, + timerType: 1, + requestType: 2, + } + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + if (params.id) { + this.record = (await api.sysTimersDetail({ id: params.id })).data + } + this.record = { + ...this.record, + } + //#region 从后端转换成前段所需格式 + //#endregion + this.form.current && this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false, + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + render() { + const { requestType, timerType } = this.state + return ( + }> +
+
+ +
+ + + + + + + + + + + + this.setState({ requestType: e.target.value })} + > + {/* + Run + */} + + Get + + + Post + + + Put + + + Delete + + + + {requestType != 0 && ( + + + + )} + + + + + + + + this.setState({ timerType: e.target.value })} + > + + 间隔方式 + + + Cron表达式 + + + + + + {timerType == 1 && ( + + + + )} + {timerType == 2 && ( + + + 参考 + + } + /> + + )} + + + + + + + + + + + + + + + + + + this.onHouseTypeChange(e)} + > + + 并行方式 + + + 串行方式 + + + + + + + + + + + + + + ) + } +} diff --git a/Web/src/pages/system/timers/index.jsx b/Web/src/pages/system/timers/index.jsx new file mode 100644 index 0000000..210818d --- /dev/null +++ b/Web/src/pages/system/timers/index.jsx @@ -0,0 +1,318 @@ +import React, { Component } from 'react' +import { Button, Card, Form, Input, message as Message, Popconfirm } from 'antd' +import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' +import { api } from 'common/api' +import auth from 'components/authorized/handler' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import { toCamelCase } from 'util/format' +import FormBody from './form' + +/** + * 配置页面所需接口函数 + */ +const apiAction = { + page: api.sysTimersPage, + add: api.sysTimersAdd, + edit: api.sysTimersEdit, + delete: api.sysTimersDelete, + start: api.sysTimersStart, + stop: api.sysTimersStop, +} + +const name = '任务计划' +const authName = 'sysTimers' +export default class index extends Component { + state = { + codes: { + requestType: [ + { code: '0', value: 'Run' }, + { code: '1', value: 'Get' }, + { code: '2', value: 'Post' }, + { code: '3', value: 'Put' }, + { code: '4', value: 'Delete' }, + ], + timerStatus: [ + { code: '0', value: '运行中' }, + { code: '1', value: '已停止' }, + { code: '2', value: '执行失败' }, + { code: '3', value: '已取消' }, + ], + }, + } + // 表格实例 + table = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + columns = [ + { + title: '任务名称', + width: 80, + dataIndex: 'jobName', + }, + { + title: '请求地址', + width: 300, + dataIndex: 'requestUrl', + }, + { + title: '请求类型', + width: 80, + dataIndex: 'requestType', + render: text => <>{this.bindCodeValue(text, 'request_type')}, + }, + { + title: '请求参数', + width: 300, + dataIndex: 'requestParameters', + }, + { + title: '间隔时间', + width: 80, + dataIndex: 'interval', + }, + { + title: 'Cron', + width: 300, + dataIndex: 'cron', + }, + { + title: '执行次数', + width: 80, + dataIndex: 'runNumber', + }, + { + title: '状态', + width: 80, + dataIndex: 'timerStatus', + render: (text, record) => ( + <> + {(text == 0 || text == 1 || text == 2) && ( + + text == 0 + ? this.onStop(record.jobName) + : this.onStart(record.jobName) + } + > + {this.bindCodeValue(text, 'timer_status')} + + )} + {text == 3 && this.bindCodeValue(text, 'timer_status')} + + ), + }, + { + title: '备注', + width: 120, + dataIndex: 'remark', + }, + ] + + /** + * 构造函数,在渲染前动态添加操作字段等 + * @param {*} props + */ + constructor(props) { + super(props) + + const flag = auth({ [authName]: [['edit'], ['delete']] }) + + if (flag) { + this.columns.push({ + title: '操作', + width: 150, + dataIndex: 'actions', + render: (text, { id }) => ( + + + this.onOpen(this.editForm, id)}>编辑 + + + this.onDelete(id)} + > + 删除 + + + + ), + }) + } + } + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + const { onLoading, onLoadData } = this.table.current + onLoading() + onLoadData() + // getDictData('house_company_type').then(codes => { + // this.setState({ codes }, () => { + // onLoadData() + // }) + // }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id) { + modal.current.open({ id }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + const { onLoading, onLoaded, onReloadData } = this.table.current + onLoading() + try { + if (action) { + await action + } + if (successMessage) { + Message.success(successMessage) + } + onReloadData() + } catch { + onLoaded() + } + } + + /** + * 删除 + * @param {*} id + */ + onDelete(id) { + this.onAction(apiAction.delete({ id }), '删除成功') + } + + onStart(jobName) { + this.onAction(apiAction.start({ jobName }), '启动成功') + } + onStop(jobName) { + this.onAction(apiAction.stop({ jobName }), '停止成功') + } + + render() { + return ( + +
+ + + + + + + } + operator={ + + + + } + /> + + + + this.table.current.onReloadData()} + > + + + + + + this.table.current.onReloadData()} + > + + + +
+ ) + } +} diff --git a/Web/src/pages/system/user/data.jsx b/Web/src/pages/system/user/data.jsx new file mode 100644 index 0000000..2e5d0b7 --- /dev/null +++ b/Web/src/pages/system/user/data.jsx @@ -0,0 +1,115 @@ +import React, { Component } from 'react' +import { Form, Spin, TreeSelect } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import { api } from 'common/api' + +export default class data extends Component { + state = { + // 加载状态 + loading: true, + + options: { + orgData: [], + areaData: [], + orgCheckedKeys: [], + }, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + id = '' + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + async fillData(params) { + this.id = params.id + //#region 从后端转换成前段所需格式 + const orgData = await this.loadOrgData() + const areaData = await this.loadAreaData() + const orgCheckedKeys = await this.loadMemberOwn(this.id) + this.setState({ + options: { + orgData, + areaData, + orgCheckedKeys, + }, + }) + this.form.current.setFieldsValue({ + id: this.id, + grantOrgIdList: orgCheckedKeys, + grantAreaCodeList: [], + }) + + this.setState({ + loading: false, + }) + } + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.id) { + postData.id = this.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + async loadOrgData() { + const { data } = await api.getOrgTree() + return data + } + + async loadAreaData() { + const { data } = await api.getAreaTree() + return data + } + async loadMemberOwn(id) { + const { data } = await api.sysUserOwnData({ id }) + return data + } + render() { + return ( + + }> +
+ + + + + + +
+
+ + ) + } +} diff --git a/Web/src/pages/system/user/form.jsx b/Web/src/pages/system/user/form.jsx new file mode 100644 index 0000000..2b47441 --- /dev/null +++ b/Web/src/pages/system/user/form.jsx @@ -0,0 +1,276 @@ +import React, { Component } from 'react' +import { + Button, + Row, + Col, + Form, + Input, + DatePicker, + Radio, + Table, + Select, + Spin, + TreeSelect, +} from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import getDictData from 'util/dic' +import { EMPTY_ID } from 'util/global' +import { api } from 'common/api' +import moment from 'moment' + +const initialValues = { + sex: 0, + sysEmpParam: {}, +} + +export default class form extends Component { + state = { + // 加载状态 + loading: true, + codes: { + orgType: [], + }, + + options: { + orgData: [], + posData: [], + }, + sysEmpParam: { + extIds: [], + }, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + + /** + * 填充数据 + * 可以在设置this.record之后对其作出数据结构调整 + * [异步,必要] + * @param {*} params + */ + async fillData(params) { + //#region 从后端转换成前段所需格式 + if (params.id) { + this.record = (await api.sysUserDetail({ id: params.id })).data + } + const orgData = await this.loadOrgData() + const posData = await this.loadPosData() + const codes = await getDictData('org_type') + + // 日期特殊处理 + if (this.record.birthday) { + this.record.birthday = moment(this.record.birthday) + } + + // 提交的时候是"param",而获取下来却是"info",在这里转换一下 + if (this.record.sysEmpInfo) { + this.record.sysEmpParam = this.record.sysEmpInfo + delete this.record.sysEmpInfo + } else if (!this.record.sysEmpParam) { + this.record.sysEmpParam = { + extIds: [], + } + } + + // 转换职位信息列表 + if (this.record.sysEmpParam.positions) { + this.record.sysEmpParam.posIdList = this.record.sysEmpParam.positions.map(p => p.posId) + } + + // 附加信息 + if (this.record.sysEmpParam.extOrgPos) { + this.record.sysEmpParam.extIds = this.record.sysEmpParam.extOrgPos.map((p, i) => { + return { + key: i, + orgId: p.orgId, + posId: p.posId, + } + }) + } + + if (params.orgId) { + this.record.sysEmpParam.orgId = params.orgId + } + + this.setState({ + codes, + options: { + ...this.state.options, + orgData, + posData, + }, + sysEmpParam: { + ...this.record.sysEmpParam, + }, + }) + + this.record = { + ...this.record, + } + //#endregion + this.form.current.setFieldsValue(this.record) + + this.setState({ + loading: false, + }) + } + + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.record) { + postData.id = this.record.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + + //#region 自定义方法 + async loadOrgData() { + const { data } = await api.getOrgTree() + return data + } + async loadPosData() { + const { data } = await api.sysPosList() + return data + } + //#endregion + + render() { + return ( + + }> +

基本信息

+
+ + + + + + + + + + + + + + + + + 保密 + + + + + + + + + + + + + + + + + + + + +
+

员工信息

+
+ + + + + + + + + +
+
+ + ) + } +} diff --git a/Web/src/pages/system/user/index.jsx b/Web/src/pages/system/user/index.jsx new file mode 100644 index 0000000..d87b3ad --- /dev/null +++ b/Web/src/pages/system/user/index.jsx @@ -0,0 +1,389 @@ +import React, { Component } from 'react' +import { + Button, + Card, + Descriptions, + Form, + Input, + List, + message as Message, + Popconfirm, + Select, + Switch, + Dropdown, + Menu, +} from 'antd' +import { AntIcon, Auth, Container, Image, ModalForm, QueryList, QueryTreeLayout } from 'components' +import { api } from 'common/api' +import { toCamelCase } from 'util/format' +import { isEqual } from 'lodash' +import getDictData from 'util/dic' +import FormBody from './form' +import RoleForm from './role' +import DataForm from './data' +import auth from 'components/authorized/handler' + +// 配置页面所需接口函数 +const apiAction = { + tree: api.getOrgTree, + page: api.getUserPage, + add: api.sysUserAdd, + edit: api.sysUserEdit, + delete: api.sysUserDelete, + + changeStatus: api.sysUserChangeStatus, + resetPwd: api.sysUserResetPwd, + + grantRole: api.sysUserGrantRole, + grantData: api.sysUserGrantData, +} + +// 用于弹窗标题 +const name = '用户' + +export default class index extends Component { + state = { + codes: { + sex: [], + commonStatus: [], + }, + } + + // 表格实例 + list = React.createRef() + + // 新增窗口实例 + addForm = React.createRef() + // 编辑窗口实例 + editForm = React.createRef() + + roleForm = React.createRef() + dataForm = React.createRef() + // 树选中节点 + selectId = undefined + + /** + * 阻止外部组件引发的渲染,提升性能 + * 可自行添加渲染条件 + * [必要] + * @param {*} props + * @param {*} state + * @returns + */ + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) + } + + /** + * 加载字典数据,之后开始加载表格数据 + * 如果必须要加载字典数据,可直接对表格设置autoLoad=true + */ + componentDidMount() { + this.list.current.onLoading() + getDictData('sex', 'common_status').then(res => { + this.setState( + { + codes: res, + }, + () => { + this.list.current.onLoadData() + } + ) + }) + } + + /** + * 调用加载数据接口,可在调用前对query进行处理 + * [异步,必要] + * @param {*} params + * @param {*} query + * @returns + */ + loadData = async (params, query) => { + query = { + ...query, + sysEmpParam: { + orgId: this.selectId, + }, + } + + const { data } = await apiAction.page({ + ...params, + ...query, + }) + return data + } + + /** + * 调用树结构数据接口 + * [异步,必要] + * @returns + */ + loadTreeData = async () => { + const { data } = await apiAction.tree() + return data + } + + /** + * 树节点选中事件 + * [必要] + * @param {*} id + */ + onSelectTree(id) { + this.selectId = id + this.list.current.onReloadData() + } + + /** + * 绑定字典数据 + * @param {*} code + * @param {*} name + * @returns + */ + bindCodeValue(code, name) { + name = toCamelCase(name) + const codes = this.state.codes[name] + if (codes) { + const c = codes.find(p => p.code == code) + if (c) { + return c.value + } + } + return null + } + + /** + * 打开新增/编辑弹窗 + * @param {*} modal + * @param {*} id + */ + onOpen(modal, id) { + modal.current.open({ + orgId: this.selectId, + id, + }) + } + + /** + * 对表格上的操作进行统一处理 + * [异步] + * @param {*} action + * @param {*} successMessage + */ + async onAction(action, successMessage) { + this.list.current.onLoading() + try { + await action + Message.success(successMessage) + this.list.current.onReloadData() + } catch { + this.list.current.onLoaded() + } + } + + /** + * 删除 + * @param {*} id + */ + onDelete(id) { + this.onAction(apiAction.delete({ id }), '删除成功') + } + + //#region 自定义方法 + renderItem(record) { + const { id, account, name, nickName, avatar, sex, phone, email, status } = record + return ( + + this.onOpen(this.editForm, id)}>编辑 + , + + this.onDelete(id)} + > + 删除 + + , + + this.onResetPassword(id)}>重置密码 + , + + + {auth('sysUser:grantRole') && ( + + this.onOpen(this.roleForm, id)}> + 授权角色 + + + )} + {auth('sysUser:grantData') && ( + + this.onOpen(this.dataForm, id)}> + 授权额外数据 + + + )} + + } + > + + 授权 + + + + , + ]} + > + } + /> + } + title={nickName || name} + description={account} + /> + + + {this.bindCodeValue(sex, 'sex')} + + {phone || '未设置'} + {email || '未设置'} + +
+ +
+ this.onSetUserStatus(id, checked)} + /> +
+
+
+
+ ) + } + + onSetUserStatus(id, checked) { + this.onAction(apiAction.changeStatus({ id, status: +!checked }), '设置成功') + } + + onResetPassword(id) { + this.onAction(apiAction.resetPwd({ id }), '重置成功') + } + //#endregion + + render() { + return ( + this.onSelectTree(key)} + > + + + + + + + + + + + + + + } + operator={ + + } + renderItem={record => this.renderItem(record)} + /> + + + + this.list.current.onReloadData()} + > + + + + this.list.current.onReloadData()} + > + + + + this.list.current.onReloadData()} + > + + + + this.list.current.onReloadData()} + > + + + + ) + } +} diff --git a/Web/src/pages/system/user/role.jsx b/Web/src/pages/system/user/role.jsx new file mode 100644 index 0000000..131cc68 --- /dev/null +++ b/Web/src/pages/system/user/role.jsx @@ -0,0 +1,99 @@ +import React, { Component } from 'react' +import { Form, Spin, Select } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import { api } from 'common/api' + +export default class role extends Component { + state = { + // 加载状态 + loading: true, + + options: { + roleData: [], + }, + roles: [], + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + id = '' + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + async fillData(params) { + this.id = params.id + //#region 从后端转换成前段所需格式 + const roleData = await this.loadRoleData() + const roles = await this.loadRole(this.id) + this.setState({ + options: { + roleData, + }, + roles, + }) + this.form.current.setFieldsValue({ + id: this.id, + grantRoleIdList: roles, + }) + + this.setState({ + loading: false, + }) + } + /** + * 获取数据 + * 可以对postData进行数据结构调整 + * [异步,必要] + * @returns + */ + async getData() { + const form = this.form.current + + const valid = await form.validateFields() + if (valid) { + const postData = form.getFieldsValue() + if (this.id) { + postData.id = this.id + } + //#region 从前段转换后端所需格式 + //#endregion + return postData + } + } + async loadRoleData() { + const { data } = await api.getRolePage() + return data.items + } + async loadRole(id) { + const { data } = await api.sysUserOwnRole({ id }) + return data + } + render() { + return ( + + }> +
+ + + +
+
+ + ) + } +} diff --git a/Web/src/reportWebVitals.js b/Web/src/reportWebVitals.js new file mode 100644 index 0000000..5253d3a --- /dev/null +++ b/Web/src/reportWebVitals.js @@ -0,0 +1,13 @@ +const reportWebVitals = onPerfEntry => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/Web/src/router/config.js b/Web/src/router/config.js new file mode 100644 index 0000000..ef515d8 --- /dev/null +++ b/Web/src/router/config.js @@ -0,0 +1,11 @@ +import Main from '../views/main' +import Login from '../views/login' +import Error404 from '../views/error/404' + +var routes = [ + { path: '/', name: 'main', component: Main, auth: true }, + { path: '/login', name: 'login', component: Login }, + { path: '/404', name: '404', component: Error404 } +] +// auth 是否需要登录 +export default routes; \ No newline at end of file diff --git a/Web/src/router/index.jsx b/Web/src/router/index.jsx new file mode 100644 index 0000000..f381887 --- /dev/null +++ b/Web/src/router/index.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Switch, BrowserRouter } from 'react-router-dom' +import NavigationGuards from './navigationGuards' +import RouterConfig from './config' + +const router = () => { + return ( + + + + + + ) +} + +export default router \ No newline at end of file diff --git a/Web/src/router/navigationGuards.jsx b/Web/src/router/navigationGuards.jsx new file mode 100644 index 0000000..1fee369 --- /dev/null +++ b/Web/src/router/navigationGuards.jsx @@ -0,0 +1,26 @@ +import React, { Component } from 'react' +import { Route, Redirect } from 'react-router-dom' + +import { token } from '../common/token' + +export default class navigationGuards extends Component { + render() { + const { routerConfig, location } = this.props + const { pathname } = location + + const targetRouterConfig = routerConfig.find((item) => { + return item.path.replace(/\s*/g, '') === pathname + }) + + if (token.value) { + if (pathname === '/login') { + return + } + } else if (!token.value && pathname !== '/login') { + return + } + + const { component } = targetRouterConfig; + return + } +} \ No newline at end of file diff --git a/Web/src/store/index.js b/Web/src/store/index.js new file mode 100644 index 0000000..0dae1fe --- /dev/null +++ b/Web/src/store/index.js @@ -0,0 +1,54 @@ +import { createStore } from 'redux' +import { cloneDeep, result, isEqual } from 'lodash' +import reducer from './reducer' + +const _store = createStore(reducer) + +const store = cloneDeep(_store) + +/** + * 允许传入第一个参数path,只监听指定属性路径的对象 + * @param {...any} args + * @returns + */ +store.subscribe = (...args) => { + let path, + listener, + snapshot + + if (typeof args[0] === 'string' && typeof args[1] === 'function') { + path = args[0] + listener = args[1] + snapshot = cloneDeep(result(_store.getState(), path)) + } else { + listener = args[0] + } + + return _store.subscribe((...args) => { + const state = _store.getState() + if (path) { + const resultState = cloneDeep(result(state, path)) + if (!isEqual(snapshot, resultState)) { + listener.apply(this, [resultState, ...args]) + } + snapshot = resultState + } else { + listener.apply(this, [state, ...args]) + } + }) +} + +/** + * 允许传入参数path,只返回指定属性路径的对象 + * @param {*} path + * @returns + */ +store.getState = (path) => { + if (path) { + return result(_store.getState(), path) + } + + return _store.getState() +} + +export default store \ No newline at end of file diff --git a/Web/src/store/reducer/business.js b/Web/src/store/reducer/business.js new file mode 100644 index 0000000..cad0e2a --- /dev/null +++ b/Web/src/store/reducer/business.js @@ -0,0 +1,33 @@ +const business = (state = {}, action) => { + switch (action.type) { + case 'PATROL_INIT_GRADE_BY_COMPLETED_DATE': + { + const completedDate = state.completedDate || [] + const { date } = action + const record = completedDate.find(p => p.id === date.id) + if (record) { + record.value = date.value + } else { + completedDate.push(date) + } + const _state = { ...state, completedDate } + return _state + } + case 'PATROL_REMOVE_INIT_GRADE_BY_COMPLETED_DATE': + { + const completedDate = state.completedDate || [] + const record = completedDate.find(p => p.id === action.id) + if (!record) { + return state + } else { + completedDate.splice(completedDate.indexOf(record), 1) + const _state = { ...state, completedDate } + return _state + } + } + default: + return state + } +} + +export default business \ No newline at end of file diff --git a/Web/src/store/reducer/dict-data.js b/Web/src/store/reducer/dict-data.js new file mode 100644 index 0000000..da7a016 --- /dev/null +++ b/Web/src/store/reducer/dict-data.js @@ -0,0 +1,11 @@ +const dictData = (state = {}, action) => { + switch (action.type) { + case 'ADD_DICT_DATA': + const _state = { ...state, ...action.value } + return _state + default: + return state + } +} + +export default dictData \ No newline at end of file diff --git a/Web/src/store/reducer/index.js b/Web/src/store/reducer/index.js new file mode 100644 index 0000000..506ebbb --- /dev/null +++ b/Web/src/store/reducer/index.js @@ -0,0 +1,18 @@ +import { combineReducers } from 'redux' +import user from './user' +import layout from './layout' +import nav from './nav' +import dictData from './dict-data' +import notice from './notice' +import business from './business' + +const combine = combineReducers({ + user, + layout, + nav, + dictData, + notice, + business +}) + +export default combine \ No newline at end of file diff --git a/Web/src/store/reducer/layout.js b/Web/src/store/reducer/layout.js new file mode 100644 index 0000000..776fdc9 --- /dev/null +++ b/Web/src/store/reducer/layout.js @@ -0,0 +1,62 @@ +import { SETTING_KEY } from "common/storage" +import { SIDER_BREAK_POINT } from "util/global" + +const defaultState = { + siderCollapsed: false, + allowSiderCollapsed: true, + theme: 'default' +} + +const localStorageState = () => { + return JSON.parse(window.localStorage.getItem(SETTING_KEY)) || {} +} + +const mergeState = { + ...defaultState, + ...localStorageState() +} + +const layout = (state = mergeState, action) => { + switch (action.type) { + // 打开窗口 + case 'OPEN_WINDOW': + return state + // 关闭窗口 + case 'CLOSE_WINDOW': + return state + // 重新加载窗口 + case 'RELOAD_WINDOW': + return state + // 侧边收起状态 + case 'TOGGLE_COLLAPSED': + { + if (window.innerWidth <= SIDER_BREAK_POINT) { + return state + } + const _state = { ...state, siderCollapsed: action.siderCollapsed } + window.localStorage.setItem(SETTING_KEY, JSON.stringify(_state)) + return _state + } + // 自动收起侧边 + case 'AUTO_TOGGLE_COLLAPSED': + { + const _state = { + ...state, + siderCollapsed: localStorageState().siderCollapsed || action.siderCollapsed, + allowSiderCollapsed: !action.siderCollapsed + } + return _state + } + // 切换主题 + case 'SET_THEME': + { + const _state = { ...state, theme: action.theme } + window.localStorage.setItem(SETTING_KEY, JSON.stringify(_state)) + return _state + } + default: + return state + } +} + +export default layout \ No newline at end of file diff --git a/Web/src/store/reducer/nav.js b/Web/src/store/reducer/nav.js new file mode 100644 index 0000000..22f8086 --- /dev/null +++ b/Web/src/store/reducer/nav.js @@ -0,0 +1,16 @@ +const defaultState = { nav: [] } + +const nav = (state = defaultState, action) => { + // 写入各种action对应的操作 + switch (action.type) { + case 'SET_NAV': + const _state = { ...state, nav: action.nav } + return _state + case 'RESET_NAV': + return defaultState + default: + return state + } +} + +export default nav \ No newline at end of file diff --git a/Web/src/store/reducer/notice.js b/Web/src/store/reducer/notice.js new file mode 100644 index 0000000..eaa4370 --- /dev/null +++ b/Web/src/store/reducer/notice.js @@ -0,0 +1,40 @@ +const defaultState = { + count: 0, + list: [] +} + +const layout = (state = defaultState, action) => { + switch (action.type) { + case 'SET_NOTICE_COUNT': + { + const _state = { + ...state, + count: action.count + } + return _state + } + case 'SET_NOTICE_LIST': + { + const _state = { + ...state, + list: action.list + } + return _state + } + case 'READ_NOTICE': + { + const notice = state.list.find(p => p.id === action.id) + if (notice && !notice.readStatus) { + notice.readStatus = 1 + state.count -= 1 + } + return { + ...state + } + } + default: + return state + } +} + +export default layout \ No newline at end of file diff --git a/Web/src/store/reducer/pane.js b/Web/src/store/reducer/pane.js new file mode 100644 index 0000000..e69de29 diff --git a/Web/src/store/reducer/user.js b/Web/src/store/reducer/user.js new file mode 100644 index 0000000..1bdacea --- /dev/null +++ b/Web/src/store/reducer/user.js @@ -0,0 +1,16 @@ +const defaultState = {} + +const user = (state = defaultState, action) => { + // 写入各种action对应的操作 + switch (action.type) { + case 'SET_USER_ACCOUNT': + const _state = { ...state, ...action.user } + return _state + case 'RESET_USER_ACCOUNT': + return defaultState + default: + return state + } +} + +export default user \ No newline at end of file diff --git a/Web/src/util/des/index.js b/Web/src/util/des/index.js new file mode 100644 index 0000000..3ae482f --- /dev/null +++ b/Web/src/util/des/index.js @@ -0,0 +1,34 @@ +import { + TripleDES, + enc, + mode, + pad +} from 'crypto-js'; + +const KEY = process.env.VUE_APP_DEV_KEY; +const key = enc.Utf8.parse(KEY); + +//TripleDES加密 +const encryptByDES = (message) => { + let encrypted = TripleDES.encrypt(message, key, { + mode: mode.ECB, + padding: pad.Pkcs7 + }); + return encrypted.toString(); +} + +//TripleDES解密 +const decryptByDES = (ciphertext) => { + let decrypted = TripleDES.decrypt({ + ciphertext: enc.Base64.parse(ciphertext) + }, key, { + mode: mode.ECB, + }); + const value = decrypted.toString(enc.Utf8); + return value; +} + +export { + encryptByDES, + decryptByDES +} diff --git a/Web/src/util/dic/index.js b/Web/src/util/dic/index.js new file mode 100644 index 0000000..ba3e28e --- /dev/null +++ b/Web/src/util/dic/index.js @@ -0,0 +1,45 @@ +import { api } from 'common/api' +import { mapKeys } from 'lodash' +import store from 'store' +import { toCamelCase } from 'util/format' + +const { getState, dispatch } = store + +const getDictData = async (...args) => { + const dictData = getState('dictData') + let dict = {} + const code = [] + for (let i = 0; i < args.length; i++) { + const codeName = toCamelCase(args[i]) + if (!dictData.hasOwnProperty(codeName)) { + code.push(args[i]) + } else { + dict[codeName] = dictData[codeName] + } + } + + if (code.length) { + try { + const value = mapKeys((await api.sysDictTypeDropDowns({ code })).data, (value, key) => { + return toCamelCase(key) + }) + + dispatch({ + type: 'ADD_DICT_DATA', + value + }) + + dict = { ...dict, ...value } + } + catch { } + } + + const result = {} + args.forEach(p => { + const codeName = toCamelCase(p) + result[codeName] = dict[codeName] + }) + return result +} + +export default getDictData \ No newline at end of file diff --git a/Web/src/util/file/index.js b/Web/src/util/file/index.js new file mode 100644 index 0000000..5b73342 --- /dev/null +++ b/Web/src/util/file/index.js @@ -0,0 +1,125 @@ +import { api } from 'common/api' + +const _getFileTypeByBase64 = (base64) => { + let arr = base64.split(','), + fileType = arr[0].match(/:(.*?);/)[1] + return fileType +} + +/** + * ArrayBuffer转Base64 + * @param {*} arrayBuffer + * @returns + */ +export const ArrayBufferToBase64 = async (arrayBuffer) => { + const blob = ArrayBufferToBlob(arrayBuffer) + const base64 = await BlobToBase64(blob) + return base64 +} + +/** + * ArrayBuffer转Blob + * @param {*} arrayBuffer + * @returns + */ +export const ArrayBufferToBlob = (arrayBuffer) => { + return new Blob([arrayBuffer]) +} + +/** + * Base64转Blob + * @param {*} base64 + * @returns + */ +export const Base64ToBlob = (base64) => { + let arr = base64.split(','), + fileType = _getFileTypeByBase64(base64), + bstr = atob(arr[1]), + n = bstr.length, + u8arr = new Uint8Array(n) + + while (n--) { + u8arr[n] = bstr.charCodeAt(n) + } + return new Blob([u8arr], { type: fileType }) +} + +/** + * Blob转文件 + * @param {*} blob + * @param {*} fileName + * @param {*} fileType + * @returns + */ +export const BlobToFile = (blob, fileName, fileType) => { + blob.lastModifiedDate = new Date() + blob.name = fileName + const file = new File([blob], fileName, { type: fileType }) + return file +} + +/** + * Base64转文件 + * @param {*} base64 + * @param {*} fileName + * @returns + */ +export const Base64ToFile = (base64, fileName) => { + const blob = Base64ToBlob(base64) + const fileType = _getFileTypeByBase64(base64) + const file = BlobToFile(blob, fileName, fileType) + return file +} + +/** + * Blob转Base64 + * @param {*} blob + * @returns + */ +export const BlobToBase64 = async (blob) => { + return new Promise(resolve => { + const reader = new FileReader() + reader.readAsDataURL(blob) + reader.onload = (e) => { + resolve(e.target.result) + } + }) +} + +export const GetFileName = (disposition) => { + const dispositionSplit = disposition.split(';') + const fileName = decodeURI( + dispositionSplit[1].split('=')[1], + dispositionSplit[2].match(/(?<=\*=).*?(?=')/)[0] + ) + return fileName +} + +export const PreviewFileResponse = async (id) => { + return api.sysFileInfoPreview({ id }).then((res) => { + return res + }) +} + +export const PreviewFileArrayBuffer = async (id) => { + const response = await PreviewFileResponse(id) + return response.data +} + +export const PreviewFileBase64 = async (id) => { + const file = await PreviewFile(id) + if (!file) return + const base64 = await BlobToBase64(file) + return base64 +} + +export const PreviewFile = async (id) => { + const response = await PreviewFileResponse(id) + const arrayBuffer = response.data + const blob = ArrayBufferToBlob(arrayBuffer) + const disposition = response.headers['content-disposition'] + if (!disposition) return + const fileName = GetFileName(disposition) + const file = BlobToFile(blob, fileName, response.headers['content-type']) + return file +} \ No newline at end of file diff --git a/Web/src/util/format/index.js b/Web/src/util/format/index.js new file mode 100644 index 0000000..238c7fe --- /dev/null +++ b/Web/src/util/format/index.js @@ -0,0 +1,50 @@ +export const numberToChinese = (val) => { + const num = parseInt(val) + const changeNum = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'] + const unit = ['', '十', '百', '千', '万'] + const getWan = (temp) => { + const strArr = temp.toString().split('').reverse() + let newNum = '' + for (var i = 0; i < strArr.length; i++) { + newNum = (i === 0 && strArr[i] === '0' ? '' : i > 0 && strArr[i] === '0' && strArr[i - 1] === '0' ? '' : changeNum[strArr[i]] + (strArr[i] === '0' ? unit[0] : unit[i])) + newNum + } + return newNum + } + const overWan = Math.floor(num / 10000) + let noWan = num % 10000 + if (noWan.toString().length < 4) noWan = '0' + noWan + + const chinanum = overWan ? getWan(overWan) + '万' + getWan(noWan) : getWan(num) + return chinanum +} + +/** + * 下划线转驼峰 + * @param {String} str + */ +export const toCamelCase = (str) => { + if (typeof str === 'string') { + return str.toLowerCase().split('_').map((p, i) => { + if (i > 0) { + return p[0].toUpperCase() + p.slice(1) + } else { + return p + } + }).join('') + } + return str +} + +/** + * 驼峰转下划线 + * @param {String} str + */ +export const toUnderScoreCase = (str) => { + if (typeof str === 'string') { + str = str.replace(/[A-Z]/g, (match) => { + return `_${match}` + }).toLowerCase() + return str.startsWith('_') ? str.slice(1) : str + } + return str +} \ No newline at end of file diff --git a/Web/src/util/global/index.js b/Web/src/util/global/index.js new file mode 100644 index 0000000..bd5639a --- /dev/null +++ b/Web/src/util/global/index.js @@ -0,0 +1,49 @@ +import { urls } from 'common/api' +import { GLOBAL_INFO_KEY } from 'common/storage' +import { encryptByDES, decryptByDES } from 'util/des' + +/** + * 空GUID + */ +export const EMPTY_ID = '00000000-0000-0000-0000-000000000000' + +/** + * 文件预览地址 + */ +export const PERVIEW_URL = process.env.REACT_APP_BASE_URL + urls.sysFileInfoPreview[0] + + +/** + * 全局信息设置 + */ +export const setGlobal = (info) => { + //app.$set(app.global, 'info', info) + window.sessionStorage.setItem(GLOBAL_INFO_KEY, encryptByDES(JSON.stringify(info))) +} +export const removeGlobal = () => { + //app.$set(app.global, 'info', undefined) + window.sessionStorage.removeItem(GLOBAL_INFO_KEY) +} +export const getGlobal = () => { + return JSON.parse(decryptByDES(window.sessionStorage.getItem(GLOBAL_INFO_KEY))) +} + +/** + * RSA公钥 + */ +export const RSA_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC273zAyijb3uX6O66VThrdfHtzZJn3d/SBM8qiETS7PijyNY3zNecAB+F/owxOWSB/6ojBo5Eu0FCiENxfpenTZB7sKrYu6NVH5gkfHLa6jz4pNzlGP93Q6RON4KjMZolAfRevBQ7vD6sOfJfMDnYi8xk+dRXcqc6PWY8fQiGs5QIDAQAB-----END PUBLIC KEY-----' + +/** + * 城市名称 + */ +export const CITY = '黄石市' + +/** + * + */ +export const AMAP_WEBAPI_KEY = 'ca01719fe09757131a1249c273619a17' + +/** + * 响应式响应宽度 + */ +export const SIDER_BREAK_POINT = 1366 \ No newline at end of file diff --git a/Web/src/util/query/index.js b/Web/src/util/query/index.js new file mode 100644 index 0000000..cd5050c --- /dev/null +++ b/Web/src/util/query/index.js @@ -0,0 +1,109 @@ +import moment from 'moment' + +/** + * 从键值对的query类型转换成数组类型 + * 键:自动作为field值 + * 值:得到一个数组作为value的值 + * queryType:一个json类型,已query的键为键,QueryType为值. 如果是一个QueryType的数组,则自动对应到value中的各个值 + * 示例: + * + getSearchInfo({ + query: { + value: '123', + text: '123', + code: 'abc', + check: ['1', '2', '3'], + range: [1, 10] + }, + queryType: { + text: QueryType.Equal, + code: QueryType.Like, + check: QueryType.Equal, + range: [QueryType.GreaterThanOrEqual, QueryType.LessThan] + } + }) + + => + + [ + { field: 'value', value: ['123'] }, + { field: 'text', value: ['123'], type: '=' }, + { field: 'code', value: ['abc'], type: 'like' }, + { field: 'check', value: ['1', '2', '3'], type: '=' }, + { field: 'range', value: [1], type: '>=' }, + { field: 'range', value: [10], type: '<' } + ] + * @param {*} param0 + * @returns [{ field: '', value: [], type: '' } ...] + */ +export const getSearchInfo = ({ query, queryType }) => { + const searchInfo = [] + Object.keys(query).forEach((p) => { + if (queryType && queryType.hasOwnProperty(p) && queryType[p].constructor === Array) { + queryType[p].forEach((q, i) => { + if (query[p] !== null && query[p] !== undefined) { + const _searchInfo = { + field: p, + value: [query[p][i]], + type: q, + } + searchInfo.push(_searchInfo) + } + }) + } else { + const _searchInfo = { + field: p, + value: [], + type: undefined, + } + + if (query[p] !== null && query[p] !== undefined) { + if (query[p].constructor === Array) { + _searchInfo.value = query[p] + } else { + _searchInfo.value = [query[p]] + } + } else { + return false + } + + if (queryType && queryType.hasOwnProperty(p)) { + _searchInfo.type = queryType[p] + } + + searchInfo.push(_searchInfo) + } + }) + + return searchInfo +} + +/** + * 获取查询用时间范围数组 + * 在这里会自动将第二个时间增加1天 + * 如果选择的日期范围为2021-01-01~2021-01-10,最终需要取得 >=2021-01-01 and <2021-01-11 的结果 + * @param {*} range 时间范围数组 + * @param {*} format 格式化 + * @returns + */ +export const getSearchDateRange = (range, format = 'YYYY-MM-DD', unit = 'days') => { + if (Array.isArray(range) && range.length === 2) { + range[1] = moment(range[1]).add(1, unit) + range = range.map(p => moment(p).format(format)) + } + return range +} + +/** + * 查询条件类型 + */ +export const QueryType = { + GreaterThan: '>', + GreaterThanOrEqual: '>=', + LessThan: '<', + LessThanOrEqual: '<=', + Like: 'LIKE', + Equal: '=', + StartWith: 'STRAT', + EndWith: 'END' +} \ No newline at end of file diff --git a/Web/src/util/rsa/index.js b/Web/src/util/rsa/index.js new file mode 100644 index 0000000..8047452 --- /dev/null +++ b/Web/src/util/rsa/index.js @@ -0,0 +1,13 @@ +import { JSEncrypt } from 'jsencrypt' +/** + * RSA加解密 + */ +const encryptByRSA = (message, publicKey) => { + const Encrypt = new JSEncrypt() + Encrypt.setPublicKey(publicKey) + return Encrypt.encrypt(message) +} + +export { + encryptByRSA +} \ No newline at end of file diff --git a/Web/src/util/tool/index.js b/Web/src/util/tool/index.js new file mode 100644 index 0000000..239faaf --- /dev/null +++ b/Web/src/util/tool/index.js @@ -0,0 +1,23 @@ +import { first, last } from "lodash" + +export const checkboxCheckedNone = (arg) => { + let { value, length, noneValue, required } = arg + if (length === undefined) length = 2 + if (noneValue === undefined) noneValue = '' + if (required === undefined) required = false + + if (first(value) === noneValue && value.length > 1) { + // 在'无'之后选中其他值 + value.shift() + } else if ( + value.length >= length + || + (last(value) === noneValue && value.length > 1) + || + (!value.length && required) + ) { + // 在其他值之后选中'无' + value = [noneValue] + } + return value +} \ No newline at end of file diff --git a/Web/src/views/error/404.jsx b/Web/src/views/error/404.jsx new file mode 100644 index 0000000..e0354a3 --- /dev/null +++ b/Web/src/views/error/404.jsx @@ -0,0 +1,11 @@ +import React, { Component } from 'react' + +export default class index extends Component { + render() { + return ( +
+ 404 +
+ ) + } +} diff --git a/Web/src/views/login/index.jsx b/Web/src/views/login/index.jsx new file mode 100644 index 0000000..0e9b331 --- /dev/null +++ b/Web/src/views/login/index.jsx @@ -0,0 +1,235 @@ +import React, { Component } from 'react' +import { Alert, Button, Form, Input, message as Message, Modal } from 'antd' +import Container from 'components/container' +import { encryptByRSA } from 'util/rsa' +import { RSA_PUBLIC_KEY } from 'util/global' +import { api } from 'common/api' +import { token } from 'common/token' + +export default class index extends Component { + state = { + loading: false, + + focusUser: false, + focusPassword: false, + + btnDisabled: true, + + pattern: '', + descriptions: '', + visible: false, + } + + backgroundImage = require(`assets/image/login-bg-0${Math.floor(Math.random() * 4)}.jpg`) + + focus = { + user: false, + password: false, + } + + form = React.createRef() + + onLogin = values => { + this.setState({ loading: true }) + + let { account, password } = values + password = encryptByRSA(password, RSA_PUBLIC_KEY) + api.login({ account, password }) + .then(({ success, data, message }) => { + if (success) { + const { passed, pattern, descriptions, token } = data + // 简单密码需要更改 + if (!passed) { + this.setState({ + visible: true, + loading: false, + btnDisabled: true, + pattern, + descriptions, + }) + } else { + this.onLoginSuccess(token) + } + } else { + this.setState({ loading: false }) + Message.error(message) + } + }) + .catch(({ message }) => { + if (typeof message === 'object' && message[0]) { + Message.error(message[0].messages[0]) + } + this.setState({ loading: false }) + }) + } + + onLoginPass = values => { + this.setState({ loading: true }) + const account = this.form.current.getFieldValue('account') + let { password, newPassword } = values + password = encryptByRSA(password, RSA_PUBLIC_KEY) + newPassword = encryptByRSA(newPassword, RSA_PUBLIC_KEY) + const confirm = newPassword // 前端验证两次密码即可.不需要加密 + + api.loginPass({ account, password, newPassword, confirm }) + .then(({ success, data, message }) => { + if (success) { + const { passed, pattern, descriptions, token } = data + // 简单密码需要更改 + if (!passed) { + this.setState({ + visible: true, + loading: false, + btnDisabled: true, + pattern, + descriptions, + }) + } else { + this.onLoginSuccess(token) + } + } else { + this.setState({ loading: false }) + Message.error(message) + } + }) + .catch(({ message }) => { + if (typeof message === 'object' && message[0]) { + Message.error(message[0].messages[0]) + } + this.setState({ loading: false }) + }) + } + + onLoginSuccess(jwtToken) { + token.value = jwtToken + Message.success('登录成功') + this.props.history.replace('/') + } + + render() { + const { loading, focusUser, focusPassword, btnDisabled, visible, pattern, descriptions } = + this.state + + return ( +
+ +
+ +
{ + this.setState({ + btnDisabled: !values.account || !values.password, + }) + }} + > + + { + this.setState({ + focusUser: !!this.form.current.getFieldValue('account'), + }) + }} + onFocus={() => { + this.setState({ focusUser: true }) + }} + size="large" + autoComplete="off" + placeholder={focusUser && '请输入用户名/手机号/邮箱'} + /> + + + { + this.setState({ + focusPassword: + !!this.form.current.getFieldValue('password'), + }) + }} + onFocus={() => { + this.setState({ focusPassword: true }) + }} + size="large" + autoComplete="off" + placeholder={focusPassword && '请输入密码'} + /> + + + + + +
+
+ + +
+
+
+ + + + + + + ({ + validator(_, value) { + if (!value || getFieldValue('newPassword') === value) { + return Promise.resolve() + } + return Promise.reject(new Error('确认新密码不匹配')) + }, + }), + ]} + name="confirm" + > + + +
+ + + + +
+
+ ) + } +} diff --git a/Web/src/views/main/_layout/content/index.jsx b/Web/src/views/main/_layout/content/index.jsx new file mode 100644 index 0000000..b806188 --- /dev/null +++ b/Web/src/views/main/_layout/content/index.jsx @@ -0,0 +1,252 @@ +import React, { Component } from 'react' +import { Divider, Layout, Tabs, Menu, Dropdown } from 'antd' +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' +import AntIcon from 'components/ant-icon' +import { Container } from 'components' + +NProgress.configure({ + parent: '.ant-layout-content > .yo-tab-external-mount > .yo-tab-external-mount-content', +}) + +class ComponentDynamic extends Component { + state = { + // 组件内部组件的key,用于刷新 + key: null, + component: null, + } + + shouldComponentUpdate() { + if (this.props.onRef) { + this.props.onRef(this) + } + return true + } + + componentDidMount() { + this.loadComponent() + } + + loadComponent() { + NProgress.start() + + // 在这里使用setTimeout调用,是防止打开窗口时卡顿 + setTimeout(async () => { + let component + + try { + component = await import(`../../../../pages${this.props.path}`) + } catch { + component = await import('views/error/404') + } + + this.setState( + { + key: Math.random().toString(16).slice(2), + component: component.default, + }, + () => { + NProgress.done() + } + ) + }) + } + + render() { + if (this.state.component) { + return ( + + + Ewide Core ©2021 v1.0 + + + } + /> + ) + } + return <> + } +} + +class Iframe extends Component { + shouldComponentUpdate() { + if (this.props.onRef) { + this.props.onRef(this) + } + return true + } + + componentDidMount() { + if (this.props.onRef) { + this.props.onRef(this) + } + this.loadComponent() + } + + loadComponent() { + NProgress.start() + const iframe = this.refs.content + iframe.onload = () => { + NProgress.done() + } + iframe.onerror = () => { + NProgress.done() + } + iframe.src = this.props.src + } + + render() { + const { title } = this.props + + return