This commit is contained in:
2021-06-30 11:43:20 +08:00
98 changed files with 5392 additions and 1618 deletions

View File

@@ -626,6 +626,11 @@
房屋详细信息
</summary>
</member>
<member name="T:Ewide.Application.Service.HouseSafety.HouseQuery.HouseQueryService">
<summary>
住宅查询
</summary>
</member>
<member name="M:Ewide.Application.Service.HouseMemberService.QueryMemberPageList(Ewide.Core.Service.UserInput)">
<summary>
分页查询用户

View File

@@ -29,6 +29,7 @@ namespace Ewide.Application
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; }

View File

@@ -120,7 +120,7 @@ WHERE HC.Id=@HouseCodeId", new { houseTask.HouseCodeId }
var houseEntity = await _houseInfoRep.DetachedEntities.FirstOrDefaultAsync(h => h.HouseCodeId == input.houseCode.Id);
//建档审核通过的房屋数据修改时对应的建档任务Task不处理
if (houseEntity.State != 6)
if (houseEntity == null || houseEntity.State != 6)
{
var houseTask = input.PatrolInfo.Adapt<BsHouseTask>();
houseTask.HouseCodeId = input.houseCode.Id;

View File

@@ -13,6 +13,7 @@ using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
namespace Ewide.Application.Service
{
@@ -108,10 +109,10 @@ WHERE 1=1";
var users = await _dapperRepository.QueryPageData<UserOutput>(sql, input, param);
foreach (var user in users.Items)
{
user.SysEmpInfo = await _sysEmpService.GetEmpInfo(user.Id);
}
//foreach (var user in users.Items)
//{
// user.SysEmpInfo = await _sysEmpService.GetEmpInfo(user.Id);
//}
return PageDataResult<UserOutput>.PageResult(users);
}
@@ -170,7 +171,10 @@ WHERE 1=1";
[UnitOfWork]
public async Task DeleteUser(DeleteUserInput input)
{
// 片区监管员在本片区已有安全员的情况下无法删除
/*
* 未处理逻辑
* 片区监管员在本片区已有安全员的情况下无法删除
*/
await _sysUserService.DeleteUser(input);
}
@@ -185,6 +189,7 @@ WHERE 1=1";
public async Task UpdateUser(UpdateUserInput input)
{
/*
* 未处理逻辑
* 如果移动组织架构,会产生以下几种逻辑
* 片区1监管员(无安全员或有其他监管员)=>片区2,直接成功
* 片区1监管员(有安全员并且无其他监管员)=>片区2,无法移动
@@ -203,7 +208,30 @@ WHERE 1=1";
[HttpGet("/houseMember/detail")]
public async Task<dynamic> GetUser([FromQuery] QueryUserInput input)
{
return await _sysUserService.GetUser(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<UserOutput>(sql, new { input.Id })).SingleOrDefault();
if (user != null)
{
user.SysEmpInfo = await _sysEmpService.GetEmpInfo(user.Id);
}
return user;
}
/// <summary>
@@ -254,7 +282,7 @@ WHERE 1=1";
var _sysOrgRep = Db.GetRepository<SysOrg>();
var org = await _sysOrgRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == orgId);
// 如果当前组织为街道, 则直接返回安全员
if (org == null || org.Type <= 30)
if (org == null || org.Type <= (int)OrgType.)
{
return roles.LastOrDefault();
}
@@ -265,7 +293,7 @@ WHERE 1=1";
var roleIds = await _sysUserRoleRep.DetachedEntities.Where(p => userIds.Contains(p.SysUserId)).Select(p => p.SysRoleId).ToListAsync();
var _sysRoleRep = Db.GetRepository<SysRole>();
var isExistZoneManager = await _sysRoleRep.DetachedEntities.AnyAsync(p => roleIds.Contains(p.Id) && p.Code == System.Enum.GetName(HouseManagerRole.ZoneManager).ToUnderScoreCase());
var isExistZoneManager = await _sysRoleRep.DetachedEntities.AnyAsync(p => roleIds.Contains(p.Id) && p.Code == Enum.GetName(HouseManagerRole.ZoneManager).ToUnderScoreCase());
// 存在片区监管员,返回安全员, 否则返回监管员
if (isExistZoneManager)
{
@@ -282,7 +310,10 @@ WHERE 1=1";
[NonAction]
private async Task<List<SysRole>> GetRoleRange()
{
var codes = System.Enum.GetNames(typeof(HouseManagerRole)).Select(p => p.ToUnderScoreCase());
var codes = (new[] {
Enum.GetName(HouseManagerRole.ZoneManager),
Enum.GetName(HouseManagerRole.HouseSecurityManager),
}).Select(p => p.ToUnderScoreCase());
var _sysRoleRep = Db.GetRepository<SysRole>();
var roles = await _sysRoleRep.DetachedEntities.Where(p => codes.Contains(p.Code)).ToListAsync();

View File

@@ -88,6 +88,7 @@ namespace Ewide.Application.Service.HouseProjectInfo
var areaCodeRep = Db.GetRepository<SysAreaCode>();
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)

View File

@@ -0,0 +1,14 @@
using Ewide.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ewide.Application
{
public class HouseQueryInput: PageInputBase
{
}
}

View File

@@ -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
{
}
}

View File

@@ -0,0 +1,81 @@
using Dapper;
using Ewide.Core.Extension;
using Furion.DatabaseAccessor;
using Furion.DependencyInjection;
using Furion.DynamicApiController;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ewide.Application.Service.HouseSafety.HouseQuery
{
/// <summary>
/// 住宅查询
/// </summary>
[ApiDescriptionSettings(Name = "HouseQuery", Order = 210)]
public class HouseQueryService : IHouseQueryService, IDynamicApiController, ITransient
{
private readonly IRepository<BsHouseCode> _houseCodeRep;
private readonly IRepository<BsHouseInfo> _houseInfoRep;
private readonly IRepository<BsHouseTask> _houseTaskRep;
private readonly IDapperRepository _dapperRepository;
public HouseQueryService(IRepository<BsHouseCode> HouseCodeRep, IRepository<BsHouseInfo> HouseInfoRep, IRepository<BsHouseTask> HouseTaskRep, IDapperRepository dapperRepository)
{
_houseCodeRep = HouseCodeRep;
_houseInfoRep = HouseInfoRep;
_houseTaskRep = HouseTaskRep;
_dapperRepository = dapperRepository;
}
[HttpPost("/houseQuery/page")]
public async Task<dynamic> 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"
});
}
}
}

View File

@@ -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
{
}
}

View File

@@ -40,7 +40,7 @@ namespace Ewide.Application.Service
[HttpPost("/houseTask/page")]
public async Task<dynamic> 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,'(',Proj.Note,')') FullProjName,HC.HouseCode,HC.Address,T.EndTime,HC.Type,HC.Industry,HC.No FROM `bs_house_task` T
var sql = @"SELECT T.Id,AA.Name AreaName,RA.Name RoadName,CA.Name CommName,Proj.AreaCode,Proj.Note,Proj.Name,CONCAT(Proj.Name,'(',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
@@ -56,7 +56,7 @@ WHERE {0}";
var param = new DynamicParameters();
if (userRoles.Where(r => r.Code == Enum.GetName(HouseManagerRole.HouseSecurityManager).ToUnderScoreCase()).Count() > 0)
{
sql = String.Format(sql, " (T.Status !=3) AND T.UserID=@UserID ");
sql = String.Format(sql, " T.UserID=@UserID ");
param.Add("UserID", user.Id);
}
@@ -66,7 +66,7 @@ WHERE {0}";
param.Add("ZoneId", userOrg.Id);
}
return await _dapperRepository.QueryPageDataDynamic(sql, input, param, filterFields: new string[] { "Type", "Address", "HouseCode" });
return await _dapperRepository.QueryPageDataDynamic(sql, input, param, filterFields: new string[] { "Type", "Address", "HouseCode", "Status","State" });
}
[HttpPost("/houseTask/submit")]

View File

@@ -20,9 +20,9 @@ namespace Ewide.Core
MENU = 1,
/// <summary>
/// 按钮
/// 功能
/// </summary>
[Description("按钮")]
BTN = 2
[Description("功能")]
FUNCTION = 2
}
}

View File

@@ -2417,9 +2417,9 @@
菜单
</summary>
</member>
<member name="F:Ewide.Core.MenuType.BTN">
<member name="F:Ewide.Core.MenuType.FUNCTION">
<summary>
按钮
功能
</summary>
</member>
<member name="T:Ewide.Core.MenuWeight">
@@ -4933,7 +4933,7 @@
路由元信息(路由附带扩展信息)
</summary>
</member>
<member name="P:Ewide.Core.Service.AntDesignTreeNode.Path">
<member name="P:Ewide.Core.Service.AntDesignTreeNode.Link">
<summary>
路径
</summary>
@@ -4943,6 +4943,11 @@
控制路由和子路由是否显示在 sidebar
</summary>
</member>
<member name="P:Ewide.Core.Service.AntDesignTreeNode.OpenType">
<summary>
打开方式
</summary>
</member>
<member name="T:Ewide.Core.Service.Meta">
<summary>
路由元信息内部类
@@ -5068,6 +5073,11 @@
菜单类型(字典 0目录 1菜单 2按钮
</summary>
</member>
<member name="P:Ewide.Core.Service.AddMenuInput.OpenType">
<summary>
打开方式(字典 0无 1组件 2内链 3外链
</summary>
</member>
<member name="P:Ewide.Core.Service.DeleteMenuInput.Id">
<summary>
菜单Id
@@ -5138,6 +5148,16 @@
关联显示父级
</summary>
</member>
<member name="P:Ewide.Core.Service.MenuTreeOutput.Permission">
<summary>
权限标识
</summary>
</member>
<member name="P:Ewide.Core.Service.MenuTreeOutput.Remark">
<summary>
备注
</summary>
</member>
<member name="P:Ewide.Core.Service.MenuTreeOutput.Sort">
<summary>
排序,越小优先级越高

View File

@@ -8,6 +8,8 @@ using System.Diagnostics;
using System.Security.Claims;
using System.Threading.Tasks;
using UAParser;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Ewide.Core
{
@@ -34,12 +36,29 @@ namespace Ewide.Core
var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
var descAtt = Attribute.GetCustomAttribute(actionDescriptor.MethodInfo, typeof(DescriptionAttribute)) as DescriptionAttribute;
var message = "请求成功";
if (isRequestSucceed)
{
var result = actionContext.Result;
var resultType = result.GetType();
if (resultType.Name == "ContentResult")
{
var resultContent = ((Microsoft.AspNetCore.Mvc.ContentResult)actionContext.Result)?.Content;
var resultJson = JsonConvert.DeserializeObject<JObject>(resultContent);
message = resultJson["message"]?.ToString();
}
}
else
{
message = actionContext.Exception.Message;
}
var sysOpLog = new SysLogOp
{
Name = descAtt != null ? descAtt.Description : actionDescriptor.ActionName,
OpType = 1,
Success = isRequestSucceed,
//Message = isRequestSucceed ? "成功" : "失败",
Message = message,
Ip = httpContext.GetRemoteIpAddressToIPv4(),
Location = httpRequest.GetRequestUrlAddress(),
Browser = clent.UA.Family + clent.UA.Major,
@@ -48,13 +67,13 @@ namespace Ewide.Core
ClassName = context.Controller.ToString(),
MethodName = actionDescriptor.ActionName,
ReqMethod = httpRequest.Method,
//Param = JsonSerializerUtility.Serialize(context.ActionArguments),
//Result = JsonSerializerUtility.Serialize(actionContext.Result),
Param = JsonConvert.SerializeObject(context.ActionArguments),
// Result = resultContent,
ElapsedTime = sw.ElapsedMilliseconds,
OpTime = DateTime.Now,
Account = httpContext.User?.FindFirstValue(ClaimConst.CLAINM_ACCOUNT)
};
await sysOpLog.InsertAsync();
await sysOpLog.InsertNowAsync();
}
}
}

View File

@@ -93,11 +93,17 @@ namespace Ewide.Core.Service.Area
#endif
}
var query = cachedAreaCodes.Where(p => p.LevelType <= level);
var resAreaCode = new List<SysAreaCode>();
if (areaCodeList != null)
{
query = query.Where(p => areaCodeList.Contains(p.Code));
foreach (var code in areaCodeList)
{
var queryRes = query.Where(p => p.Code.StartsWith(code));
resAreaCode.AddRange(queryRes);
}
cachedAreaCodes = resAreaCode.Distinct().ToList();
}
cachedAreaCodes = query.ToList();
return new TreeBuildUtil<AreaTreeNode>().DoTreeBuild(cachedAreaCodes.Select(u => new AreaTreeNode
{
Code = u.Code,

View File

@@ -101,6 +101,24 @@ namespace Ewide.Core.Service
// 设置刷新Token令牌
_httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = refreshToken;
// 增加登录日志
var loginOutput = user.Adapt<LoginOutput>();
var clent = Parser.GetDefault().Parse(App.GetService<IHttpContextAccessor>().HttpContext.Request.Headers["User-Agent"]);
loginOutput.LastLoginBrowser = clent.UA.Family + clent.UA.Major;
loginOutput.LastLoginOs = clent.OS.Family + clent.OS.Major;
await new SysLogVis
{
Name = "登录",
Success = true,
Message = "登录成功",
Ip = loginOutput.LastLoginIp,
Browser = loginOutput.LastLoginBrowser,
Os = loginOutput.LastLoginOs,
VisType = 1,
VisTime = loginOutput.LastLoginTime,
Account = loginOutput.Account
}.InsertAsync();
return accessToken;
}
@@ -163,20 +181,6 @@ namespace Ewide.Core.Service
loginOutput.Menus = await _sysMenuService.GetLoginMenusAntDesign(userId, defaultActiveAppCode);
}
// 增加登录日志
//await new SysLogVis
//{
// Name = "登录",
// Success = true,
// Message = "登录成功",
// Ip = loginOutput.LastLoginIp,
// Browser = loginOutput.LastLoginBrowser,
// Os = loginOutput.LastLoginOs,
// VisType = 1,
// VisTime = loginOutput.LastLoginTime,
// Account = loginOutput.Account
//}.InsertAsync();
return loginOutput;
}

View File

@@ -1,4 +1,5 @@
using Ewide.Core.Extension;
using Dapper;
using Ewide.Core.Extension;
using Furion.DatabaseAccessor;
using Furion.DatabaseAccessor.Extensions;
using Furion.DependencyInjection;
@@ -20,10 +21,12 @@ namespace Ewide.Core.Service
public class SysOpLogService : ISysOpLogService, IDynamicApiController, ITransient
{
private readonly IRepository<SysLogOp> _sysOpLogRep; // 操作日志表仓储
private readonly IDapperRepository<SysLogOp> _dapperRepository;
public SysOpLogService(IRepository<SysLogOp> sysOpLogRep)
public SysOpLogService(IRepository<SysLogOp> sysOpLogRep, IDapperRepository<SysLogOp> dapperRepository)
{
_sysOpLogRep = sysOpLogRep;
_dapperRepository = dapperRepository;
}
/// <summary>
@@ -54,11 +57,7 @@ namespace Ewide.Core.Service
[HttpPost("/sysOpLog/delete")]
public async Task ClearOpLog()
{
var opLogs = await _sysOpLogRep.Entities.ToListAsync();
opLogs.ForEach(u =>
{
u.Delete();
});
await _dapperRepository.ExecuteAsync("DELETE FROM sys_log_op");
}
}
}

View File

@@ -38,12 +38,17 @@
/// <summary>
/// 路径
/// </summary>
public string Path { get; set; }
public string Link { get; set; }
/// <summary>
/// 控制路由和子路由是否显示在 sidebar
/// </summary>
public bool Hidden { get; set; }
/// <summary>
/// 打开方式
/// </summary>
public int OpenType { get; set; }
}
/// <summary>

View File

@@ -25,7 +25,7 @@ namespace Ewide.Core.Service
/// <summary>
/// 菜单类型(字典 0目录 1菜单 2按钮
/// </summary>
public virtual string Type { get; set; }
public virtual int Type { get; set; }
/// <summary>
/// 图标
@@ -55,7 +55,7 @@ namespace Ewide.Core.Service
/// <summary>
/// 打开方式(字典 0无 1组件 2内链 3外链
/// </summary>
public virtual string OpenType { get; set; }
public virtual int OpenType { get; set; }
/// <summary>
/// 是否可见Y-是N-否)
@@ -99,7 +99,13 @@ namespace Ewide.Core.Service
/// 菜单类型(字典 0目录 1菜单 2按钮
/// </summary>
[Required(ErrorMessage = "菜单类型不能为空")]
public override string Type { get; set; }
public override int Type { get; set; }
/// <summary>
/// 打开方式(字典 0无 1组件 2内链 3外链
/// </summary>
[Required(ErrorMessage = "打开方式不能为空")]
public override int OpenType { get; set; }
}
public class DeleteMenuInput

View File

@@ -38,6 +38,16 @@ namespace Ewide.Core.Service
/// </summary>
public bool VisibleParent { get; set; }
/// <summary>
/// 权限标识
/// </summary>
public string Permission { get; set; }
/// <summary>
/// 备注
/// </summary>
public string Remark { get; set; }
/// <summary>
/// 排序,越小优先级越高
/// </summary>

View File

@@ -53,7 +53,7 @@ namespace Ewide.Core.Service
var roleIdList = await _sysUserRoleService.GetUserRoleIdList(userId);
var menuIdList = await _sysRoleMenuService.GetRoleMenuIdList(roleIdList);
permissions = await _sysMenuRep.DetachedEntities.Where(u => menuIdList.Contains(u.Id))
.Where(u => u.Type == (int)MenuType.BTN)
.Where(u => u.Type == (int)MenuType.FUNCTION)
.Where(u => u.Status == (int)CommonStatus.ENABLE)
.Select(u => u.Permission).ToListAsync();
#if DEBUG
@@ -83,8 +83,8 @@ namespace Ewide.Core.Service
sysMenuList = await _sysMenuRep.DetachedEntities
.Where(u => u.Status == (int)CommonStatus.ENABLE)
.Where(u => u.Application == appCode)
.Where(u => u.Type != (int)MenuType.BTN)
//.Where(u => u.Weight != (int)MenuWeight.DEFAULT_WEIGHT)
.Where(u => u.Type != (int)MenuType.FUNCTION)
.Where(u => u.Weight != (int)MenuWeight.DEFAULT_WEIGHT)
.OrderBy(u => u.Sort).ToListAsync();
}
else
@@ -96,7 +96,7 @@ namespace Ewide.Core.Service
.Where(u => menuIdList.Contains(u.Id))
.Where(u => u.Status == (int)CommonStatus.ENABLE)
.Where(u => u.Application == appCode)
.Where(u => u.Type != (int)MenuType.BTN)
.Where(u => u.Type != (int)MenuType.FUNCTION)
.OrderBy(u => u.Sort).ToListAsync();
}
// 转换成登录菜单
@@ -106,8 +106,9 @@ namespace Ewide.Core.Service
Pid = u.Pid,
Name = u.Code,
Component = u.Component,
Redirect = u.OpenType == (int)MenuOpenType.OUTER ? u.Link : u.Redirect,
Path = u.OpenType == (int)MenuOpenType.OUTER ? u.Link : u.Router,
Redirect = u.Redirect,
Link = u.Link,
OpenType = u.OpenType,
Meta = new Meta
{
Title = u.Name,
@@ -137,7 +138,7 @@ namespace Ewide.Core.Service
var roleIdList = await _sysUserRoleService.GetUserRoleIdList(userId);
var menuIdList = await _sysRoleMenuService.GetRoleMenuIdList(roleIdList);
return await _sysMenuRep.DetachedEntities
.Where(u => u.VisibleParent)
.Where(u => (u.Type == 2 && u.VisibleParent) || u.Type < 2)
.Where(u => menuIdList.Contains(u.Id))
.Where(u => u.Status == (int)CommonStatus.ENABLE)
.Select(u => u.Application).ToListAsync();
@@ -185,7 +186,7 @@ namespace Ewide.Core.Service
/// 增加和编辑时检查参数
/// </summary>
/// <param name="input"></param>
private static void CheckMenuParam(MenuInput input)
private async Task CheckMenuParam(MenuInput input)
{
var type = input.Type;
var router = input.Router;
@@ -195,17 +196,17 @@ namespace Ewide.Core.Service
if (type.Equals((int)MenuType.DIR))
{
if (string.IsNullOrEmpty(router))
throw Oops.Oh(ErrorCode.D4001);
//if (string.IsNullOrEmpty(router))
// throw Oops.Oh(ErrorCode.D4001);
}
else if (type.Equals((int)MenuType.MENU))
{
if (string.IsNullOrEmpty(router))
throw Oops.Oh(ErrorCode.D4001);
if (string.IsNullOrEmpty(openType))
throw Oops.Oh(ErrorCode.D4002);
//if (string.IsNullOrEmpty(router))
// throw Oops.Oh(ErrorCode.D4001);
//if (string.IsNullOrEmpty(openType))
// throw Oops.Oh(ErrorCode.D4002);
}
else if (type.Equals((int)MenuType.BTN))
else if (type.Equals((int)MenuType.FUNCTION))
{
if (string.IsNullOrEmpty(permission))
throw Oops.Oh(ErrorCode.D4003);
@@ -217,10 +218,37 @@ namespace Ewide.Core.Service
//if (!urlSet.Contains(permission.Replace(":","/")))
// throw Oops.Oh(ErrorCode.meu1005);
}
//按钮可以设置绑定菜单
if(!isVisibleParent && type.Equals((int)MenuType.BTN))
// 检查上级菜单的类型是否正确
var pid = input.Pid;
var flag = true;
var empty = System.Guid.Empty.ToString();
switch(type)
{
throw Oops.Oh(ErrorCode.D4004);
// 目录必须在顶级下
case (int)MenuType.DIR:
flag = pid.Equals(empty);
break;
// 菜单必须在顶级或目录下
case (int)MenuType.MENU:
if (!pid.Equals(empty))
{
var parent = await _sysMenuRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == pid);
flag = parent.Type.Equals((int)MenuType.DIR);
}
break;
// 功能必须在菜单下
case (int)MenuType.FUNCTION:
{
var parent = await _sysMenuRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == pid);
flag = parent == null ? false : parent.Type.Equals((int)MenuType.MENU);
}
break;
}
if (!flag)
{
throw Oops.Oh("父级菜单类型错误");
}
}
@@ -240,7 +268,7 @@ namespace Ewide.Core.Service
}
// 校验参数
CheckMenuParam(input);
await CheckMenuParam(input);
var menu = input.Adapt<SysMenu>();
menu.Pids = await CreateNewPids(input.Pid);
@@ -296,7 +324,7 @@ namespace Ewide.Core.Service
}
// 校验参数
CheckMenuParam(input);
await CheckMenuParam(input);
// 如果是编辑父id不能为自己的子节点
var childIdList = await _sysMenuRep.DetachedEntities.Where(u => u.Pids.Contains(input.Id.ToString()))
.Select(u => u.Id).ToListAsync();
@@ -360,7 +388,7 @@ namespace Ewide.Core.Service
// 更新当前菜单
oldMenu = input.Adapt<SysMenu>();
oldMenu.Pids = newPids;
await oldMenu.UpdateAsync(ignoreNullValues: true);
await oldMenu.UpdateExcludeAsync(new[] { nameof(SysMenu.Type) }, ignoreNullValues: true);
// 清除缓存
await _sysCacheService.DelByPatternAsync(CommonConst.CACHE_KEY_MENU);
@@ -432,6 +460,8 @@ namespace Ewide.Core.Service
Title = u.Name,
Type = u.Type,
VisibleParent = u.VisibleParent,
Permission = u.Permission,
Remark = u.Remark,
Sort = u.Sort
}).ToListAsync();
return new TreeBuildUtil<MenuTreeOutput>().DoTreeBuild(menus);

View File

@@ -86,10 +86,10 @@ namespace Ewide.Core.Service
// emps.Add(_sysEmpService.GetEmpInfo(long.Parse(u.Id)));
//});
//await Task.WhenAll(emps);
foreach (var user in users.Items)
{
user.SysEmpInfo = await _sysEmpService.GetEmpInfo(user.Id);
}
//foreach (var user in users.Items)
//{
// user.SysEmpInfo = await _sysEmpService.GetEmpInfo(user.Id);
//}
return PageDataResult<UserOutput>.PageResult(users);
}
@@ -170,8 +170,17 @@ namespace Ewide.Core.Service
if (isExist) throw Oops.Oh(ErrorCode.D1003);
var user = input.Adapt<SysUser>();
await user.UpdateExcludeAsync(new[] { nameof(SysUser.Password), nameof(SysUser.Status), nameof(SysUser.AdminType) }, true);
user.UpdateIncludeNow(new[] { nameof(SysUser.Birthday) });
await user.UpdateIncludeAsync(new[] {
nameof(SysUser.Account),
nameof(SysUser.NickName),
nameof(SysUser.Name),
nameof(SysUser.Birthday),
nameof(SysUser.Sex),
nameof(SysUser.Email),
nameof(SysUser.Phone),
nameof(SysUser.Tel),
}, true);
// user.UpdateIncludeNow(new[] { nameof(SysUser.Birthday) });
input.SysEmpParam.Id = user.Id.ToString();
// 更新员工及附属机构职位信息
await _sysEmpService.AddOrUpdate(input.SysEmpParam);

View File

@@ -1,5 +1,6 @@
using Furion.RemoteRequest.Extensions;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
@@ -26,10 +27,8 @@ namespace Ewide.Core
var ramInfo = GetRamInfo();
return new
{
TotalRam = Math.Ceiling(ramInfo.Total / 1024).ToString() + " GB", // 总内存
RamRate = Math.Ceiling(100 * ramInfo.Used / ramInfo.Total), // 内存使用率
CpuRate = Math.Ceiling(double.Parse(GetCPURate())), // cpu使用率
RunTime = GetRunTime()
};
}
@@ -40,20 +39,28 @@ namespace Ewide.Core
public static async Task<dynamic> GetMachineBaseInfo()
{
var assemblyName = typeof(Furion.App).Assembly.GetName();
//var networkInfo = NetworkInfo.GetNetworkInfo();
//var (Received, Send) = networkInfo.GetInternetSpeed(1000);
return new
{
WanIp = await GetWanIpFromPCOnline(), // 外网IP
SendAndReceived = "",// "上行" + Math.Round(networkInfo.SendLength / 1024.0 / 1024 / 1024, 2) + "GB 下行" + Math.Round(networkInfo.ReceivedLength / 1024.0 / 1024 / 1024, 2) + "GB", // 上下行流量统计
LanIp = "",//networkInfo.AddressIpv4.ToString(), // 局域网IP
LanIp = Dns.GetHostAddresses(string.Empty).Last().ToString(), // 局域网IP
IpMac = "",//networkInfo.Mac, // Mac地址
HostName = Environment.MachineName, // HostName
CurrentDirectory = Environment.CurrentDirectory, // 系统路径
SystemOs = RuntimeInformation.OSDescription, // 系统名称
OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(), // 系统架构
ProcessorCount = Environment.ProcessorCount.ToString() + "核", // CPU核心数
ProcessorCount = Environment.ProcessorCount, // CPU核心数
FrameworkDescription = RuntimeInformation.FrameworkDescription + " + " + assemblyName.Name.ToString() + assemblyName.Version.ToString(), // .NET和Furion版本
NetworkSpeed = ""//"上行" + Send / 1024 + "kb/s 下行" + Received / 1024 + "kb/s" // 网络速度
NetworkSpeed = "",//"上行" + Send / 1024 + "kb/s 下行" + Received / 1024 + "kb/s" // 网络速度
// Cpu名称
CpuName = GetCPUName(),
// Cpu基准速度
CpuBaseSpeed = GetCPUSpeed(),
// 内存总量
TotalRam = GetRamInfo().Total,
// 运行时间
RunTime = (long)(DateTime.Now - Process.GetCurrentProcess().StartTime).TotalMilliseconds,
// 磁盘信息
DiskInfo = GetDiskInfo(),
};
}
@@ -94,24 +101,86 @@ namespace Ewide.Core
return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
}
private static string GetCPUName()
{
string result;
if (IsUnix())
{
// ??????
var output = ShellUtil.Bash("");
result = output.Trim();
}
else
{
var output = ShellUtil.Cmd("wmic", "cpu get Name");
result = output.Replace("Name", string.Empty).Trim();
}
return result;
}
private static string GetCPUSpeed()
{
string result;
if (IsUnix())
{
// ??????
var output = ShellUtil.Bash("");
result = output.Trim();
}
else
{
var output = ShellUtil.Cmd("wmic", "cpu get CurrentClockSpeed");
result = output.Replace("CurrentClockSpeed", string.Empty).Trim();
}
return result;
}
private static List<Dictionary<string, string>> GetDiskInfo()
{
var result = new List<Dictionary<string, string>>();
if (IsUnix())
{
// ??????
var output = ShellUtil.Bash("");
}
else
{
var output = ShellUtil.Cmd("wmic", "LOGICALDISK get Name,Description,FileSystem,Size,FreeSpace");
var strArray = output.Replace("\r", "").Trim().Split('\n')
.Select(p => System.Text.RegularExpressions.Regex.Replace(p.Trim(), @"\s+", ",").Split(','));
var keyArray = strArray.First();
var valueArray = strArray.Where((p, i) => i > 0).ToArray();
foreach(var value in valueArray)
{
var dict = new Dictionary<string, string>();
for(var i = 0;i<keyArray.Length;i++)
{
dict.Add(keyArray[i], value[i]);
}
result.Add(dict);
}
}
return result;
}
/// <summary>
/// 获取CPU使用率
/// </summary>
/// <returns></returns>
private static string GetCPURate()
{
string cpuRate;
string result;
if (IsUnix())
{
var output = ShellUtil.Bash("top -b -n1 | grep \"Cpu(s)\" | awk '{print $2 + $4}'");
cpuRate = output.Trim();
result = output.Trim();
}
else
{
var output = ShellUtil.Cmd("wmic", "cpu get LoadPercentage");
cpuRate = output.Replace("LoadPercentage", string.Empty).Trim();
result = output.Replace("LoadPercentage", string.Empty).Trim();
}
return cpuRate;
return result;
}
/// <summary>

View File

@@ -19,7 +19,9 @@
"nprogress": "^0.2.0",
"photoswipe": "^4.1.3",
"react": "^17.0.2",
"react-color": "^2.19.3",
"react-dom": "^17.0.2",
"react-json-view": "^1.21.3",
"react-monaco-editor": "^0.43.0",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
@@ -58,4 +60,4 @@
"last 1 safari version"
]
}
}
}

View File

@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import { Form, Spin } from 'antd'
import { AntIcon } from 'components'
import { cloneDeep } from 'lodash'
import { api } from 'common/api'
const initialValues = {}
@@ -31,14 +31,15 @@ export default class form extends Component {
* @param {*} params
*/
async fillData(params) {
this.record = cloneDeep(params.record)
const state = { loading: false }
//#region 从后端转换成前段所需格式,也可以在此处调用获取详细数据接口
if (params.id) {
this.record = (await api).data
}
//#endregion
this.form.current.setFieldsValue(this.record)
this.setState({
loading: false,
})
this.setState(state)
}
/**

View File

@@ -63,16 +63,16 @@ export default class index extends Component {
title: '操作',
width: 150,
dataIndex: 'actions',
render: (text, record) => (
render: (text, { id }) => (
<QueryTableActions>
<Auth auth={{ [authName]: 'edit' }}>
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
<a onClick={() => this.onOpen(this.editForm, id)}>编辑</a>
</Auth>
<Auth auth={{ [authName]: 'delete' }}>
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
onConfirm={() => this.onDelete(id)}
>
<a>删除</a>
</Popconfirm>
@@ -145,12 +145,10 @@ export default class index extends Component {
/**
* 打开新增/编辑弹窗
* @param {*} modal
* @param {*} record
* @param {*} id
*/
onOpen(modal, record) {
modal.current.open({
record,
})
onOpen(modal, id) {
modal.current.open({ id })
}
/**
@@ -177,10 +175,10 @@ export default class index extends Component {
/**
* 删除
* @param {*} record
* @param {*} id
*/
onDelete(record) {
this.onAction(apiAction.delete(record), '删除成功')
onDelete(id) {
this.onAction(apiAction.delete({ id }), '删除成功')
}
//#region 自定义方法

View File

@@ -35,6 +35,7 @@
@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';

View File

@@ -9,7 +9,12 @@
width: 150px;
}
.ant-descriptions {
clear: both;
margin-bottom: @padding-sm;
.ant-descriptions-view {
overflow: visible;
}
&:last-child {
margin-bottom: 0;
}
@@ -26,4 +31,23 @@
}
}
}
.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;
}
}
}

View File

@@ -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;
}

View File

@@ -315,6 +315,13 @@
white-space: normal;
}
}
.ant-form-vertical {
.ant-form-item-label {
>label {
font-weight: bold;
}
}
}
.ant-form-item-required {
&::before {
content: '' !important;

View File

@@ -26,7 +26,7 @@
}
}
}
>.ant-pagination {
.ant-pagination {
margin: @padding-md 0;
}
.ant-descriptions {
@@ -44,6 +44,14 @@
}
}
}
&--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;
@@ -52,4 +60,36 @@
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%);
}
}
}
}

View File

@@ -1,5 +1,4 @@
@import (reference) '../extend.less';
.yo-query-bar {
margin-bottom: @padding-xs;
.ant-form-inline {
@@ -8,220 +7,234 @@
}
}
}
.yo-action-bar {
display: flex;
justify-content: space-between;
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;
}
}
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%);
}
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 {
>td {
transition-property: background, border-bottom-color;
}
> tr.ant-table-row:hover {
> td {
border-bottom-color: lighten(@primary-color, 30%);
}
}
>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-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;
}
>tr {
>th {
font-weight: bold;
}
}
}
.ant-table-sticky-scroll {
display: none;
}
.yo-table {
.ant-table {
margin: 0 !important;
.ant-table {
margin: 0 !important;
}
.border-right-none {
border-right-width: 0 !important;
&:last-child {
border-right-width: 1px !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-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-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-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;
display: inline-block;
vertical-align: middle;
vertical-align: middle;
&--inner {
display: flex;
align-items: center;
&--inner {
display: flex;
align-items: center;
height: 18px;
}
height: 18px;
}
}
.yo-table--column-setting {
width: 240px;
width: 240px;
.ant-dropdown-menu-item {
display: flex;
align-items: center;
justify-content: space-between;
}
.anticon-pushpin {
transition: @animation-duration-slow;
transform: rotate(45deg);
.ant-dropdown-menu-item {
display: flex;
justify-content: space-between;
align-items: center;
}
.anticon-pushpin {
color: darken(@white, 40%);
transition: @animation-duration-slow;
transform: rotate(45deg);
}
.yo-table--fixed {
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;
.yo-inner-table {
.ant-table {
.ant-table-tbody {
> .ant-table-expanded-row-level-1 > td {
padding: 0;
border-right: none !important;
.ant-table-wrapper {
border: none;
.ant-table {
margin: 0 !important;
}
.ant-table-container {
border: none;
.ant-table-cell:first-child {
padding-left: 25px;
}
.ant-table-expanded-row-level-1 > td {
border-right: @border-width-base @border-style-base @table-border-color !important;
padding: @padding-sm @padding-xs @padding-sm @padding-xl;
}
}
}
padding-right: 0 !important;
border-right: none !important;
+.ant-table-cell {
padding-left: 0;
}
}
.ant-table-tbody {
>.ant-table-expanded-row-level-1>td {
padding: 0;
border-right: none !important;
.ant-table-wrapper {
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-expanded-row-level-1>td {
padding: @padding-sm @padding-xs @padding-sm @padding-xl;
border-right: @border-width-base @border-style-base @table-border-color !important;
.ant-card {
max-width: fit-content;
margin-bottom: 0;
background: none;
.ant-card-grid {
width: 300px;
padding: @padding-xs @padding-sm;
background-color: @white;
}
}
}
}
}
}
}
}
}

View File

@@ -19,6 +19,20 @@
}
}
}
&--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;

View File

@@ -20,3 +20,27 @@
.flex {
display: flex;
}
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.ellipsis-2 {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
}
.ellipsis-3 {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
}

View File

@@ -300,6 +300,14 @@
opacity: 0;
}
>iframe {
display: block;
width: 100%;
height: 100%;
border: 0;
}
}
}
}

View File

@@ -33,3 +33,12 @@
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;
}
}

View File

@@ -1,6 +1,7 @@
const urls = {
houseInfoGetByTaskId: ['/houseInfo/getByTaskId', 'get'],
houseInfoSave: ['houseInfo/save', 'post']
houseInfoSave: ['houseInfo/save', 'post'],
houseInfoSubmitToCheck: ['/houseInfo/submitToCheck', 'post']
}
export default urls

View File

@@ -3,6 +3,7 @@ const urls = {
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'],

View File

@@ -0,0 +1,5 @@
const urls = {
houseQueryPage: ['/houseQuery/page', 'post'],
}
export default urls

View File

@@ -5,6 +5,7 @@ import houseMember from './houseMember'
import houseSelector from './houseSelector'
import houseTask from './houseTask'
import houseInfo from './houseInfo'
import houseQuery from './houseQuery'
const urls = {
...houseProjectInfo,
@@ -13,7 +14,8 @@ const urls = {
...houseMember,
...houseSelector,
...houseTask,
...houseInfo
...houseInfo,
...houseQuery
}
export default urls

View File

@@ -28,6 +28,7 @@ const urls = {
* 修改应用状态
*/
sysAppChangeStatus: ['/sysApp/changeStatus', 'post'],
sysAppDetail: ['/sysApp/detail', 'get']
}
export default urls

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react'
import { Checkbox, Descriptions, Empty, Spin, Tooltip } from 'antd'
import { Card, Checkbox, Descriptions, Empty, Popover, Spin, Tooltip } from 'antd'
import { AntIcon } from 'components'
import { EMPTY_ID } from 'util/global'
@@ -17,11 +17,11 @@ function getVisible() {
const caseChildren = checked.filter(item => item.visibleParent || item.type != 2)
const visibleParents = []
// 递归寻找父级
const findVisibleParents = (children) => {
const findVisibleParents = children => {
const parents = []
children.forEach(item => {
if (item.parentId) {
const parent = this.list.find(item => item.id === item.parentId)
const parent = this.list.find(p => p.id === item.parentId)
if (parent) {
parents.push(parent)
visibleParents.push(parent)
@@ -50,24 +50,28 @@ function getVisible() {
function renderDescriptions(data) {
return data.map(item => {
return item.children && item.children.length ? renderItem.call(this, item) : renderCheckbox.call(this, item)
return item.children && item.children.length
? renderItem.call(this, item)
: renderCheckbox.call(this, item)
})
}
function renderItem(data) {
return (
<Descriptions bordered column={1}>
<Descriptions bordered column={1} contentStyle={{ padding: 0 }}>
<Descriptions.Item
label={
<Checkbox
value={data.id}
checked={data.checked}
indeterminate={data.indeterminate}
onChange={(e) => this.onChange(e, data)}
>{data.title}</Checkbox>
onChange={e => this.onChange(e, data)}
>
{data.title}
</Checkbox>
}
>
{renderDescriptions.call(this, data.children)}
<Card bordered={false}>{renderDescriptions.call(this, data.children)}</Card>
</Descriptions.Item>
</Descriptions>
)
@@ -75,27 +79,33 @@ function renderItem(data) {
function renderCheckbox(data) {
return (
<div className="yo-authority-view--checkbox">
<Checkbox
value={data.id}
checked={data.checked}
onChange={(e) => this.onChange(e, data)}
>{data.title}</Checkbox>
{
data.visibleParent && data.type == 2 &&
<Tooltip placement="top" title="选中此项才会显示菜单">
<AntIcon type="eye" style={{ color: '#1890ff' }} className="mr-xxs" />
</Tooltip>
}
</div>
<label className="ant-card-grid ant-card-grid-hoverable">
<Popover
placement="topLeft"
content={data.remark || <span className="text-normal">没有说明</span>}
>
<Checkbox
value={data.id}
checked={data.checked}
onChange={e => this.onChange(e, data)}
>
{data.title}
</Checkbox>
{data.visibleParent && data.type == 2 && (
<Tooltip placement="bottom" title="选中此项才会显示菜单">
<AntIcon type="eye" style={{ color: '#1890ff' }} className="mr-xxs" />
</Tooltip>
)}
<div className="text-gray">{data.permission}</div>
</Popover>
</label>
)
}
export default class AuthorityView extends Component {
state = {
loading: false,
dataSource: []
dataSource: [],
}
list = []
@@ -104,7 +114,8 @@ export default class AuthorityView extends Component {
super(props)
this.autoLoad = typeof this.props.autoLoad === 'boolean' ? this.props.autoLoad : true
this.loadData = typeof this.props.loadData === 'function' ? this.props.loadData : async () => { }
this.loadData =
typeof this.props.loadData === 'function' ? this.props.loadData : async () => {}
}
/**
@@ -126,7 +137,10 @@ export default class AuthorityView extends Component {
if (this.props.defaultSelectedKeys) {
this.list.map(item => {
if (this.props.defaultSelectedKeys.includes(item.id) && (!item.children || !item.children.length)) {
if (
this.props.defaultSelectedKeys.includes(item.id) &&
(!item.children || !item.children.length)
) {
this.onSelect(true, item)
}
})
@@ -134,8 +148,10 @@ export default class AuthorityView extends Component {
this.setState({
dataSource: res,
loading: false
loading: false,
})
this.onChange()
}
onReloadData = () => {
@@ -143,16 +159,18 @@ export default class AuthorityView extends Component {
}
onChange = (e, item) => {
this.onSelect(e.target.checked, 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(item => item.checked).map(item => item.id),
this.list.filter(p => p.checked).map(p => p.id),
// 返回所有选中和半选
this.list.filter(item => item.checked || item.indeterminate).map(item => item.id),
this.list.filter(p => p.checked || p.indeterminate).map(p => p.id),
// 返回所有选中和半选,但是不返回没有子级选中visibleParent的半选
visible
)
@@ -170,7 +188,7 @@ export default class AuthorityView extends Component {
}
this.setState({
dataSource: this.list.filter(item => item.parentId === EMPTY_ID)
dataSource: this.list.filter(p => p.parentId === EMPTY_ID),
})
}
@@ -210,31 +228,30 @@ export default class AuthorityView extends Component {
return (
<div className="yo-authority-view">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
{
!this.state.loading ?
<Descriptions bordered column={1} className="yo-authority-view--container">
{
this.state.dataSource.map(item => {
return (
<Descriptions.Item
label={
<Checkbox
value={item.id}
checked={item.checked}
indeterminate={item.indeterminate}
onChange={(e) => this.onChange(e, item)}
>{item.title}</Checkbox>
}
{!this.state.loading ? (
<Descriptions bordered column={1} className="yo-authority-view--container">
{this.state.dataSource.map(item => {
return (
<Descriptions.Item
label={
<Checkbox
value={item.id}
checked={item.checked}
indeterminate={item.indeterminate}
onChange={e => this.onChange(e, item)}
>
{renderDescriptions.call(this, item.children)}
</Descriptions.Item>
)
})
}
</Descriptions>
:
<Empty className="mt-lg mb-lg" />
}
{item.title}
</Checkbox>
}
>
{renderDescriptions.call(this, item.children)}
</Descriptions.Item>
)
})}
</Descriptions>
) : (
<Empty className="mt-lg mb-lg" />
)}
</Spin>
</div>
)

View File

@@ -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 (
<Row gutter={8}>
<Col flex="1">
<Select
ref={this.select}
dropdownRender={() => (
<ChromePicker
color={color || value || '#000'}
disableAlpha
onChange={color => this.onChange(color)}
onChangeComplete={color => this.onChangeComplete(color)}
/>
)}
showArrow={false}
{...this.props}
value={value}
/>
</Col>
<Col flex="32px">
<div
className="color-selector--palette"
style={{ backgroundColor: color || value || '#000' }}
onClick={() => {
this.select.current.focus()
}}
></div>
</Col>
</Row>
)
}
}

View File

@@ -0,0 +1,46 @@
import React, { Component } from 'react'
import { Col, Input, InputNumber, Row } from 'antd'
import { AntIcon } from 'components'
export default class index extends Component {
render() {
const { unit, placeholder, value, onChange } = this.props
console.log(value)
return (
<Input.Group>
<Row align="middle">
<Col flex="1">
<InputNumber
{...this.props}
className="w-100-p"
placeholder={placeholder && placeholder[0]}
value={value && value[0]}
onChange={e => {
const result = [e, value && value[1]]
onChange && onChange(result)
}}
/>
</Col>
<span className="yo-addon">
<AntIcon className="text-gray" type="swap-right" />
</span>
<Col flex="1">
<InputNumber
{...this.props}
className="w-100-p"
placeholder={placeholder && placeholder[1]}
value={value && value[1]}
onChange={e => {
const result = [value && value[0], e]
onChange && onChange(result)
}}
/>
</Col>
{unit && <span className="yo-addon">{unit}</span>}
</Row>
</Input.Group>
)
}
}

View File

@@ -1,11 +1,13 @@
export { default as AntIcon } from 'components/ant-icon'
export { default as AuthorityView } from './authority-view'
export { default as Auth } from './authorized'
export { default as ColorSelector } from './form/color-selector'
export { default as ComponentDynamic } from './component-dynamic'
export { default as Container } from './container'
export { default as IconSelector } from './icon-selector'
export { default as Image } from './image'
export { default as ModalForm } from './modal-form'
export { default as InputNumberRange } from './form/input-number-range'
export { default as PhotoPreview } from './photo-preview'
export { default as QueryList } from './query-list'
export { default as QueryTable } from './query-table'

View File

@@ -3,56 +3,62 @@ import { Button, Drawer, message as Message, Modal } from 'antd'
import { cloneDeep, isEqual } from 'lodash'
/**
* 渲染对话框
* @param {*} props
* @param {*} on
* @param {*} childWithProps
* @returns
*/
* 渲染对话框
* @param {*} props
* @param {*} on
* @param {*} childWithProps
* @returns
*/
function renderModal(props, on, childWithProps) {
on = {
...on,
onCancel: () => this.onClose()
onCancel: () => this.onClose(),
}
return <Modal className="yo-modal-form" {...props} {...on}>
{childWithProps}
</Modal>
return (
<Modal className="yo-modal-form" {...props} {...on}>
{childWithProps}
</Modal>
)
}
/**
* 渲染抽屉
* @param {*} props
* @param {*} on
* @param {*} childWithProps
* @returns
* @param {*} props
* @param {*} on
* @param {*} childWithProps
* @returns
*/
function renderDrawer(props, on, childWithProps) {
on = {
...on,
onClose: () => this.onClose()
onClose: () => this.onClose(),
}
return <Drawer className="yo-drawer-form" {...props} {...on}>
<div class="yo-drawer-form--body">
{childWithProps}
</div>
<div className="ant-drawer-footer">
<Button onClick={on.onClose}>取消</Button>
<Button loading={this.state.confirmLoading} onClick={on.onOk} type="primary">确定</Button>
</div>
</Drawer>
// react在这里会对该组件不存在的props抛出异常
;['action', 'onSuccess', 'onOk', 'confirmLoading'].forEach(p => {
delete props[p]
})
return (
<Drawer className="yo-drawer-form" {...props} onClose={() => on.onClose()}>
<div className="yo-drawer-form--body">{childWithProps}</div>
<div className="ant-drawer-footer">
<Button onClick={on.onClose}>取消</Button>
<Button loading={this.state.confirmLoading} onClick={on.onOk} type="primary">
确定
</Button>
</div>
</Drawer>
)
}
export default class ModalForm extends Component {
state = {
// 弹窗显示/隐藏
visible: false,
// 提交状态
confirmLoading: false
confirmLoading: false,
}
// 子元素实例
@@ -67,12 +73,11 @@ export default class ModalForm extends Component {
snapshot = null
// 完成操作
action = async () => { }
action = async () => {}
// 是否在关闭时校验数据改变
compareOnClose = true
constructor(props) {
super(props)
@@ -93,7 +98,7 @@ export default class ModalForm extends Component {
/**
* 打开弹窗
* @param {*} data
* @param {*} data
*/
open = (data = {}) => {
this.data = data
@@ -110,7 +115,7 @@ export default class ModalForm extends Component {
/**
* 子元素创建后回调
* 对子元素数据进行填充,(如需关闭时对比)之后再获取结构调整后的数据快照
* @returns
* @returns
*/
onCreated = async () => {
const body = this.childNode.current
@@ -126,7 +131,7 @@ export default class ModalForm extends Component {
/**
* 取消编辑
* (如需关闭时对比)获取当前数据结构与快照对比
* @returns
* @returns
*/
onClose = async () => {
const body = this.childNode.current
@@ -143,7 +148,7 @@ export default class ModalForm extends Component {
content: '当前内容已更改,是否确认不保存并且关闭',
onOk: () => {
this.close()
}
},
})
return
}
@@ -155,7 +160,7 @@ export default class ModalForm extends Component {
/**
* 完成编辑
* 校验并获取结构调整后的数据,调用this.action进行操作
* @returns
* @returns
*/
onOk = async () => {
const body = this.childNode.current
@@ -175,39 +180,33 @@ export default class ModalForm extends Component {
this.props.onSuccess(postData)
}
}
} finally {
this.setState({ confirmLoading: false })
}
}
render() {
const props = {
...this.props,
visible: this.state.visible,
destroyOnClose: true,
confirmLoading: this.state.confirmLoading
confirmLoading: this.state.confirmLoading,
}
const on = {
onOk: () => this.onOk()
onOk: () => this.onOk(),
}
const childWithProps = React.cloneElement(
React.Children.only(this.props.children),
{
created: childNode => {
this.childNode.current = childNode
this.onCreated()
}
}
)
const childWithProps = React.cloneElement(React.Children.only(this.props.children), {
created: childNode => {
this.childNode.current = childNode
this.onCreated()
},
})
return this.type === 'modal' ?
renderModal.call(this, props, on, childWithProps)
:
renderDrawer.call(this, props, on, childWithProps)
return this.type === 'modal'
? renderModal.call(this, props, on, childWithProps)
: renderDrawer.call(this, props, on, childWithProps)
}
}

View File

@@ -1,6 +1,7 @@
import React, { Component } from 'react'
import { Button, Form, List, Pagination, Spin, Tooltip } from 'antd'
import { AntIcon } from 'components'
import { cloneDeep } from 'lodash'
const propsMap = ['autoLoad', 'loadData', 'pageIndex', 'pageSize']
@@ -43,6 +44,8 @@ export default class QueryList extends Component {
state = {
loading: false,
dataSource: [],
ping: [],
}
// 查询表单实例
@@ -91,6 +94,12 @@ export default class QueryList extends Component {
if (this.autoLoad) {
this.onLoadData()
}
window.addEventListener('resize', this.onResizeScroll)
}
componentWillUnmount() {
window.removeEventListener('resize', this.onResizeScroll)
}
/**
@@ -105,12 +114,17 @@ export default class QueryList extends Component {
pageIndex: this.pagination.current,
pageSize: this.pagination.pageSize,
},
this.query
cloneDeep(this.query)
)
this.setState({
dataSource: res.rows || res.data || res.items,
})
this.setState(
{
dataSource: res.rows || res.data || res.items,
},
() => {
this.onResizeScroll()
}
)
this.pagination.total = res.totalCount
@@ -174,7 +188,26 @@ export default class QueryList extends Component {
this.onLoadData()
}
onResizeScroll = () => {
this.onListScrollX({ target: this.refs['scroll-x'] })
}
onListScrollX(e) {
const { offsetWidth, scrollWidth, scrollLeft } = e.target
const ping = []
if (offsetWidth + scrollLeft < scrollWidth && scrollLeft > 0) {
ping.push(...['left', 'right'])
} else if (offsetWidth + scrollLeft < scrollWidth) {
ping.push('right')
} else if (offsetWidth + scrollLeft >= scrollWidth && offsetWidth < scrollWidth) {
ping.push('left')
}
this.setState({ ping })
}
render() {
const { loading, dataSource, ping } = this.state
const attrs = {}
Object.keys(this.props).forEach(key => {
if (!propsMap.includes(key)) {
@@ -206,9 +239,21 @@ export default class QueryList extends Component {
</div>
</div>
<div className="yo-list">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
<List {...props} />
{!!this.state.dataSource && !!this.state.dataSource.length && (
<Spin spinning={loading} indicator={<AntIcon type="loading" />}>
<div
className={['yo-list-container', ...ping.map(p => `yo-list--ping-${p}`)]
.filter(p => p)
.join(' ')}
>
<div
className="yo-list--scroll"
ref="scroll-x"
onScroll={e => this.onListScrollX(e)}
>
<List {...props} />
</div>
</div>
{!!dataSource && !!dataSource.length && (
<Pagination
size="small"
{...this.pagination}

View File

@@ -5,24 +5,41 @@ function renderActions() {
const { children } = this.props
const actions = []
Array.isArray(children) ? children
.filter(action => action)
.forEach((action, i) => {
actions.push(action)
if (i < this.props.children.length - 1) {
actions.push(<Divider type="vertical" key={i} />)
}
}) : (actions.push(children))
Array.isArray(children)
? children
.filter(action => action)
.forEach((action, i) => {
actions.push(action, <Divider type="vertical" key={i} />)
})
: actions.push(children)
return actions
}
export default class QueryTableActions extends Component {
componentDidMount() {
// 删除多余的间隔线
const className = 'ant-divider ant-divider-vertical'
let series = false
for (const node of this.refs.inner.childNodes) {
if (
(series && node.className == className) ||
(!node.nextElementSibling && node.className == className)
) {
node.remove()
series = false
} else if (node.className == className) {
series = true
} else {
series = false
}
}
}
render() {
return (
<div className="yo-table-actions">
<div className="yo-table-actions--inner">
<div className="yo-table-actions--inner" ref="inner">
{renderActions.call(this)}
</div>
</div>

View File

@@ -1,6 +1,7 @@
import React, { Component } from 'react'
import { Form, Button, Table, Tooltip } from 'antd'
import { Form, Button, Table, Tooltip, Drawer } from 'antd'
import { AntIcon } from 'components'
import { cloneDeep, isEqual } from 'lodash'
const propsMap = ['columns', 'autoLoad', 'loadData', 'pageIndex', 'pageSize']
@@ -33,17 +34,18 @@ const rowNoColumn = {
* @returns
*/
function renderQueryBar() {
const { query, moreQuery, onQueryChange } = this.props
const { query, moreQuery, onQueryChange, queryInitialValues } = this.props
return (
<div className="yo-query-bar">
<Form
layout="inline"
ref={this.form}
ref={this.queryForm}
onFinish={value => this.onQuery(value)}
initialValues={this.props.queryInitialValues}
initialValues={queryInitialValues}
onValuesChange={(changedValues, allValues) =>
onQueryChange && onQueryChange(changedValues, allValues)
onQueryChange &&
this.queryForm.current.setFieldsValue(onQueryChange(changedValues, allValues))
}
>
{query}
@@ -54,18 +56,73 @@ function renderQueryBar() {
</Button>
<Tooltip placement="bottom" title="重置查询">
<Button
onClick={() => this.onResetQuery()}
onClick={() => this.onResetQuery(this.queryForm)}
icon={<AntIcon type="undo" />}
/>
</Tooltip>
</Button.Group>
{moreQuery && <Button>更多查询条件</Button>}
{moreQuery && (
<Button type="text" onClick={() => this.onOpenMoreQuery()}>
更多查询条件
</Button>
)}
</Form.Item>
</Form>
</div>
)
}
function renderMoreQueryBody() {
const { moreQueryVisible } = this.state
const { moreQuery, onQueryChange, queryInitialValues } = this.props
return (
<Drawer
width={700}
title="查询"
className="yo-drawer-form"
visible={moreQueryVisible}
forceRender
onClose={() => this.setState({ moreQueryVisible: false })}
>
<Form
layout="vertical"
ref={this.moreQueryForm}
initialValues={queryInitialValues}
onValuesChange={(changedValues, allValues) =>
onQueryChange &&
this.moreQueryForm.current.setFieldsValue(
onQueryChange(changedValues, allValues)
)
}
>
<div className="yo-drawer-form--body">{moreQuery}</div>
<div className="ant-drawer-footer">
<Button.Group>
<Button
htmlType="submit"
type="primary"
icon={<AntIcon type="search" />}
onClick={() =>
this.onQuery(this.moreQueryForm.current.getFieldsValue())
}
>
查询
</Button>
<Tooltip placement="top" title="重置查询">
<Button
onClick={() => this.onResetQuery(this.moreQueryForm)}
icon={<AntIcon type="undo" />}
/>
</Tooltip>
</Button.Group>
</div>
</Form>
</Drawer>
)
}
function renderTable(props, on) {
return <Table className="yo-table" {...props} {...on} />
}
@@ -78,10 +135,14 @@ export default class QueryTable extends Component {
type: 'tree',
// 数据
dataSource: [],
moreQueryVisible: false,
}
// 查询表单实例
form = React.createRef()
queryForm = React.createRef()
// 更多查询表单实例
moreQueryForm = React.createRef()
// 查询值
query = {}
@@ -157,13 +218,17 @@ export default class QueryTable extends Component {
onLoadData = async () => {
this.onLoading()
if (isEqual(this.query, {}) && this.props.queryInitialValues) {
this.query = this.props.queryInitialValues
}
const res = await this.loadData(
{
pageIndex: this.pagination.current,
pageSize: this.pagination.pageSize,
...this.sorter,
},
this.query
cloneDeep(this.query)
)
if (res.rows || res.data || res.items) {
this.setState({
@@ -208,7 +273,13 @@ export default class QueryTable extends Component {
* @param {*} values
*/
onQuery = values => {
this.query = values
const { queryInitialValues } = this.props
this.query = {
...queryInitialValues,
...this.query,
...values,
}
this.onReloadData(true)
}
@@ -216,15 +287,25 @@ export default class QueryTable extends Component {
* 重置查询
* 初始化表单字段值,加载数据,并返回到第一页
*/
onResetQuery = () => {
const { queryInitialValues, onQueryChange } = this.props
onResetQuery = from => {
const { queryInitialValues, onQueryChange, query, moreQuery } = this.props
let queryValues = {}
if (from === this.queryForm) {
from.current.resetFields()
queryValues = moreQuery ? this.moreQueryForm.current.getFieldsValue() : {}
} else if (from === this.moreQueryForm) {
from.current.resetFields()
queryValues = query ? this.queryForm.current.getFieldsValue() : {}
}
this.form.current.resetFields()
this.query = {
...queryInitialValues,
...queryValues,
}
const values = this.form.current.getFieldsValue()
onQueryChange && onQueryChange(values, values)
const changedValues = cloneDeep(this.query)
onQueryChange && onQueryChange(changedValues, changedValues)
this.onReloadData(true)
}
@@ -272,12 +353,16 @@ export default class QueryTable extends Component {
return false
}
onOpenMoreQuery = () => {
this.setState({ moreQueryVisible: true })
}
render() {
const { rowNumber } = this
const { loading, dataSource, type } = this.state
const { query, operator, columns } = this.props
const { query, moreQuery, operator, columns, expandable, expandedRowRender } = this.props
const attrs = {}
Object.keys(this.props).forEach(key => {
@@ -292,8 +377,8 @@ export default class QueryTable extends Component {
dataSource,
columns: (() => {
const c = []
if (type !== 'tree' && rowNumber) {
c.push(rowNoColumn)
if (type !== 'tree' && rowNumber & !expandable & !expandedRowRender) {
//c.push(rowNoColumn)
}
c.push(...(columns || []))
return c.filter(p => !p.hidden)
@@ -332,6 +417,7 @@ export default class QueryTable extends Component {
) : (
renderTable.call(this, props, on)
)}
{moreQuery && renderMoreQueryBody.call(this)}
</section>
)
}

View File

@@ -1,6 +1,7 @@
import React, { Component } from 'react'
import { Breadcrumb, Empty, Input, Layout, Spin, Tooltip, Tree } from 'antd'
import { Breadcrumb, Button, Col, Empty, Input, Layout, Row, Spin, Tooltip, Tree } from 'antd'
import { AntIcon, Container } from 'components'
import { SIDER_BREAK_POINT } from 'util/global'
function generateKey(data, level) {
const n = level || [0]
@@ -23,7 +24,7 @@ function generateList(data) {
this.list.push({
key,
[this.replaceFields.value]: data[i][this.replaceFields.value],
[this.replaceFields.title]: data[i][this.replaceFields.title]
[this.replaceFields.title]: data[i][this.replaceFields.title],
})
if (data[i][this.replaceFields.children]) {
generateList.call(this, data[i][this.replaceFields.children])
@@ -32,7 +33,7 @@ function generateList(data) {
}
function getParentKey(key, tree) {
let parentKey;
let parentKey
for (let i = 0; i < tree.length; i++) {
const node = tree[i]
if (node[this.replaceFields.children]) {
@@ -43,23 +44,26 @@ function getParentKey(key, tree) {
}
}
}
return parentKey;
return parentKey
}
function renderTitle(nodeData) {
const title = nodeData[this.replaceFields.title]
if (this.state.searchValue && title.indexOf(this.state.searchValue) > -1) {
return <>
{title.substr(0, title.indexOf(this.state.searchValue))}
<span style={{ color: '#f50' }}>{this.state.searchValue}</span>
{title.substr(title.indexOf(this.state.searchValue) + this.state.searchValue.length)}
</>
return (
<>
{title.substr(0, title.indexOf(this.state.searchValue))}
<span style={{ color: '#f50' }}>{this.state.searchValue}</span>
{title.substr(
title.indexOf(this.state.searchValue) + this.state.searchValue.length
)}
</>
)
}
return <>{title}</>
}
function renderBreadcrumbItem() {
const path = ['顶级']
const findPath = (data, level) => {
@@ -91,14 +95,16 @@ function renderBreadcrumbItem() {
}
export default class QueryTreeLayout extends Component {
state = {
loading: false,
dataSource: [],
expandedKeys: [],
selectedKey: '',
autoExpandParent: true,
searchValue: ''
searchValue: '',
collapsed: false,
showSider: false,
}
list = []
@@ -108,16 +114,18 @@ export default class QueryTreeLayout extends Component {
replaceFields = {
value: 'id',
title: 'title',
children: 'children'
children: 'children',
}
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 () => { }
this.loadData =
typeof this.props.loadData === 'function' ? this.props.loadData : async () => {}
this.defaultExpanded = typeof this.props.defaultExpanded === 'boolean' ? this.props.defaultExpanded : false
this.defaultExpanded =
typeof this.props.defaultExpanded === 'boolean' ? this.props.defaultExpanded : false
if (this.props.replaceFields) {
this.replaceFields = this.props.replaceFields
@@ -131,6 +139,13 @@ export default class QueryTreeLayout extends Component {
if (this.autoLoad) {
this.onLoadData()
}
window.addEventListener('resize', this.onResizeLayout)
this.onResizeLayout()
}
componentWillUnmount() {
window.removeEventListener('resize', this.onResizeLayout)
}
/**
@@ -151,7 +166,7 @@ export default class QueryTreeLayout extends Component {
}
this.setState({
dataSource: data,
expandedKeys
expandedKeys,
})
this.onLoaded()
}
@@ -162,8 +177,8 @@ export default class QueryTreeLayout extends Component {
onLoading = () => {
this.setState({
loading: {
indicator: <AntIcon type="loading" />
}
indicator: <AntIcon type="loading" />,
},
})
}
@@ -178,28 +193,28 @@ export default class QueryTreeLayout extends Component {
this.onLoadData()
}
onExpand = (expandedKeys) => {
onExpand = expandedKeys => {
this.setState({
expandedKeys,
autoExpandParent: false
autoExpandParent: false,
})
}
onSelect = (selectedKeys) => {
onSelect = selectedKeys => {
const selectedIds = []
selectedKeys.forEach(p => {
const data = this.list.find(m => m.key === p)
selectedIds.push(data[this.replaceFields.value])
})
this.setState({
selectedKey: selectedIds[0]
selectedKey: selectedIds[0],
})
if (this.props.onSelect) {
this.props.onSelect(selectedIds[0])
}
}
onSearch = (value) => {
onSearch = value => {
const expandedKeys = this.list
.map(p => {
if (p[this.replaceFields.title].indexOf(value) > -1) {
@@ -212,34 +227,49 @@ export default class QueryTreeLayout extends Component {
this.setState({
expandedKeys,
autoExpandParent: !!value,
searchValue: value
searchValue: value,
})
}
onResizeLayout = () => {
this.setState({
collapsed: window.innerWidth <= SIDER_BREAK_POINT,
})
}
render() {
const { dataSource, expandedKeys, autoExpandParent } = this.state
const { loading, dataSource, expandedKeys, autoExpandParent, collapsed, showSider } =
this.state
const props = {
treeData: dataSource,
expandedKeys,
autoExpandParent
autoExpandParent,
}
const on = {
onExpand: (expandedKeys) => this.onExpand(expandedKeys),
onSelect: (selectedKeys) => this.onSelect(selectedKeys)
onExpand: expandedKeys => this.onExpand(expandedKeys),
onSelect: selectedKeys => this.onSelect(selectedKeys),
}
return (
<Layout className="yo-tree-layout">
<Layout.Sider width="240px">
<Layout.Sider
width="240px"
className={[
collapsed ? 'yo-tree-layout--collapsed' : '',
showSider ? 'open' : '',
]
.filter(p => p)
.join(' ')}
onMouseLeave={() => this.setState({ showSider: false })}
>
<Layout.Header>
<div className="header-actions">
<Input.Search
allowClear={true}
placeholder="请输入检索关键字"
onSearch={(value) => this.onSearch(value)}
onSearch={value => this.onSearch(value)}
/>
</div>
</Layout.Header>
@@ -248,37 +278,56 @@ export default class QueryTreeLayout extends Component {
<AntIcon type="undo" onClick={() => this.onReloadData()} />
</Tooltip>
<Tooltip placement="bottom" title="折叠全部">
<AntIcon type="switcher" onClick={() => this.setState({ expandedKeys: [] })} />
<AntIcon
type="switcher"
onClick={() => this.setState({ expandedKeys: [] })}
/>
</Tooltip>
</div>
<div className="yo-tree-layout--content">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />} wrapperClassName="h-100-p">
{
!this.state.loading && !this.state.dataSource.length ?
<Empty
image={
<div className="text-center pt-md">
<AntIcon className="h3 mt-xl mb-md" type="smile" />
<p>暂无数据</p>
</div>
}
description={false}
/>
:
<Tree
{...props}
{...on}
titleRender={(nodeData) => renderTitle.call(this, nodeData)}
/>
}
<Spin
spinning={loading}
indicator={<AntIcon type="loading" />}
wrapperClassName="h-100-p"
>
{!loading && !dataSource.length ? (
<Empty
image={
<div className="text-center pt-md">
<AntIcon className="h3 mt-xl mb-md" type="smile" />
<p>暂无数据</p>
</div>
}
description={false}
/>
) : (
<Tree
{...props}
{...on}
titleRender={nodeData => renderTitle.call(this, nodeData)}
/>
)}
</Spin>
</div>
</Layout.Sider>
<Layout.Content>
<Container mode="fluid">
<Breadcrumb className="mt-md mb-md">
{renderBreadcrumbItem.call(this)}
</Breadcrumb>
<Row gutter={16} align="middle">
{collapsed && (
<Col>
<Button
type="text"
icon={<AntIcon type="menu" />}
onClick={() => this.setState({ showSider: true })}
/>
</Col>
)}
<Col flex="1">
<Breadcrumb className="mt-md mb-md">
{renderBreadcrumbItem.call(this)}
</Breadcrumb>
</Col>
</Row>
</Container>
{this.props.children}
</Layout.Content>

View File

@@ -36,6 +36,8 @@ export default class index extends Component {
loading: false,
})
})
} else {
this.setState({ loading: false })
}
}

View File

@@ -19,6 +19,7 @@ import { cloneDeep, isEqual } from 'lodash'
import getDictData from 'util/dic'
import { api } from 'common/api'
import { CITY } from 'util/global'
import auth from 'components/authorized/handler'
const initialValues = {
type: 1,
@@ -85,6 +86,8 @@ export default class form extends Component {
//#region 从后端转换成前段所需格式
await this.initMap()
const options = { ...this.state.options }
const { data: areaTree } = await api.getAreaTree()
options.areaTree = areaTree
// 有数据
if (this.record) {
const { type, areaCode } = this.record
@@ -95,6 +98,16 @@ export default class form extends Component {
areaCode.substr(0, 9),
areaCode,
]
switch (areaTree[0].adCode.length) {
case 6:
this.record.areaCode = this.record.areaCode.slice(1)
break
case 9:
this.record.areaCode = this.record.areaCode.slice(2)
break
default:
break
}
// 获取项目和片区列表
const data = await this.getProjectsAndZones({
areaCode: this.record.areaCode,
@@ -107,12 +120,12 @@ export default class form extends Component {
this.map.setCenter(position)
}
const codes = await getDictData('house_type', 'house_industry')
const { data: areaTree } = await api.getAreaTree()
options.areaTree = areaTree
this.setState({
codes,
options,
})
this.showHouseCode()
//#endregion
this.form.current.setFieldsValue(this.record)
@@ -136,7 +149,7 @@ export default class form extends Component {
const postData = form.getFieldsValue()
//#region 从前段转换后端所需格式
if (postData.areaCode) {
postData.areaCode = postData.areaCode[3]
postData.areaCode = postData.areaCode[postData.areaCode.length - 1]
}
//#endregion
return postData
@@ -282,21 +295,22 @@ export default class form extends Component {
type = value.type
}
if (!(areaCode && areaCode.length === 4)) return null
if (!(areaCode && areaCode.length > 0 && areaCode[areaCode.length - 1].length === 12))
return null
try {
const result = {}
if (mode.includes('projects')) {
const { data: projects } = await api.houseProjectList({
areaCode: areaCode[3],
areaCode: areaCode[areaCode.length - 1],
type,
})
result.projects = projects
}
if (mode.includes('zones')) {
const { data: zones } = await api.houseZoneList({
areaCode: areaCode[3],
areaCode: areaCode[areaCode.length - 1],
})
result.zones = zones
}
@@ -323,8 +337,14 @@ export default class form extends Component {
})
} else if (values) {
const { type, industry, areaCode, projectId, no } = values
if (areaCode && areaCode.length === 4 && projectId && no) {
let houseCode = areaCode[3]
if (
areaCode &&
areaCode.length > 0 &&
areaCode[areaCode.length - 1].length === 12 &&
projectId &&
no
) {
let houseCode = areaCode[areaCode.length - 1]
const projectSort = this.state.options.projects.find(p => p.id === projectId).sort
houseCode += projectSort.toString().padStart(3, '0')
houseCode += no.toString().padStart(3, '0')
@@ -368,17 +388,20 @@ export default class form extends Component {
// 所属区域
else if (changedValues.hasOwnProperty('areaCode')) {
this.setState({ loading: true })
const data = await this.getProjectsAndZones()
form.setFieldsValue({ projectId: undefined, zoneId: undefined })
this.setState({
options: {
...this.state.options,
...data,
},
loading: false,
})
this.showHouseCode(form.getFieldsValue())
const flag = auth({ houseCode: 'getNextNoByCode' })
if (flag) {
this.setState({ loading: true })
const data = await this.getProjectsAndZones()
form.setFieldsValue({ projectId: undefined, zoneId: undefined })
this.setState({
options: {
...this.state.options,
...data,
},
loading: false,
})
this.showHouseCode(form.getFieldsValue())
}
}
// 所属项目
@@ -474,7 +497,10 @@ export default class form extends Component {
<Form.Item
name="areaCode"
rules={[
{ required: true, message: '请选择房屋所在区域' },
{
required: true,
message: '请选择房屋所在区域',
},
]}
>
<Cascader

View File

@@ -211,7 +211,7 @@ export default class index extends Component {
.padStart(3, '0')}`,
path,
param: {
id: record.id,
id: record && record.id,
},
})
// modal.current.open({

View File

@@ -157,35 +157,37 @@ export default class building extends Component {
onValuesChange(changedValues, allValues) {
const form = this.form.current
const { houseInfo } = changedValues
if (
houseInfo.hasOwnProperty('landFloorCount') ||
houseInfo.hasOwnProperty('underFloorCount')
) {
const {
houseInfo: { landFloorCount, underFloorCount },
} = allValues
form.setFieldsValue({
houseInfo: {
totalFloor: +landFloorCount + +underFloorCount,
},
})
}
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('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'),
},
})
if (houseInfo.hasOwnProperty('completedDate')) {
dispatch({
type: 'PATROL_INIT_GRADE_BY_COMPLETED_DATE',
date: {
id: this.props.id,
value: +houseInfo.completedDate.format('YYYY'),
},
})
}
}
}

View File

@@ -98,7 +98,44 @@ export default class ownership extends Component {
* @param {*} changedValues
* @param {*} allValues
*/
async onValuesChange(changedValues, 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() {

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react'
import { Button, Descriptions, message as Message, Spin, Tabs } from 'antd'
import { Form, Button, Input, Descriptions, message as Message, Modal, Spin, Tabs } from 'antd'
import { merge, isEqual } from 'lodash'
import { AntIcon, ComponentDynamic, Container } from 'components'
import { api } from 'common/api'
@@ -46,6 +46,23 @@ const tabs = [
show: true,
},
]
const actions = {
save: {
action: 'houseInfoSave',
remark: '保存',
after: 'reload',
},
submit: {
action: 'houseInfoSubmitToCheck',
remark: '提交',
after: 'close',
},
check: {
action: 'houseInfoSave',
remark: '审核',
after: 'close',
},
}
export default class index extends Component {
state = {
@@ -56,11 +73,13 @@ export default class index extends Component {
saveDisabled: true,
saving: false,
taskStatus: 0,
}
children = []
formData = {}
checkForm = React.createRef()
shouldComponentUpdate(props, state) {
return !isEqual(this.state, state)
@@ -72,6 +91,7 @@ export default class index extends Component {
if (taskId) {
api.houseInfoGetByTaskId({ taskId }).then(({ data }) => {
this.setState({
taskStatus: data.patrolInfo.status,
record: data,
loading: false,
})
@@ -86,7 +106,43 @@ export default class index extends Component {
}
}
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()
@@ -97,13 +153,51 @@ export default class index extends Component {
}
//#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
}
console.log(this.formData)
this.setState({ saving: true })
setTimeout(() => {
Message.success('提交成功')
this.setState({ saving: false })
}, 3000)
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) {
this.props.param.table.current.onReloadData()
}
switch (action.after) {
case 'close':
window.closeContentWindow()
break
default:
this.componentDidMount()
break
}
}
} finally {
this.setState({ saving: false })
}
}
// setTimeout(() => {
// Message.success('提交成功')
// this.setState({ saving: false })
// }, 3000)
//#endregion
}
@@ -117,19 +211,62 @@ export default class index extends Component {
<div className="yo-form-page--bar yo-form-page--bar--with-tab">
<Container mode="fluid">
<div className="yo-form-page--bar-inner">
<span></span>
<span>
{this.state.taskStatus == 3 && (
<Form ref={this.checkForm} layout="inline">
<Form.Item
label="审核意见"
name={['taskCheckRecord', 'content']}
rules={[
{
required: true,
message: '请输入审核意见',
},
]}
>
<Input.TextArea
autoSize
autoComplete="off"
placeholder="请输入审核意见"
className="w-500"
/>
</Form.Item>
<Form.Item>
<Button
type="primary"
onClick={() => this.onCheck(6)}
>
通过
</Button>
<Button
type="primary"
onClick={() => this.onCheck(-1)}
>
退回
</Button>
</Form.Item>
</Form>
)}
</span>
<span>
{this.state.taskStatus >= -1 && this.state.taskStatus < 3 && (
<Button
disabled={saveDisabled}
loading={saving}
type="primary"
onClick={() => this.onSave()}
>
保存
</Button>
)}
{this.state.taskStatus == 2 && (
<Button type="primary" onClick={() => this.onSubmit()}>
提交审核
</Button>
)}
<Button onClick={() => window.closeContentWindow()}>
取消
</Button>
<Button
disabled={saveDisabled}
loading={saving}
type="primary"
onClick={() => this.onSubmit()}
>
保存
</Button>
</span>
</div>
</Container>

View File

@@ -69,7 +69,8 @@ export default class handling extends Component {
//#region 从后端转换成前段所需格式
if (this.record) {
const { patrolInfo } = this.record
patrolInfo.initGrade = this.getInitGrade(getState('business').completedDate)
if (this.record.houseInfo.completedDate)
patrolInfo.initGrade = this.getInitGrade(getState('business').completedDate)
}
_state.codes = await getDictData(
'house_patrol_init_grade',
@@ -123,26 +124,24 @@ export default class handling extends Component {
render() {
const { loading, codes, initGradeValue } = this.state
console.log(initGradeValue)
return (
<Spin spinning={loading} indicator={<AntIcon type="loading" />}>
<Form {...layout} ref={this.form}>
<Form.Item
label="初始等级"
name={['patrolInfo', 'initGrade']}
tooltip="初始等级无法手动更改由房屋详情的竣工日期决定2000年之后竣工的为一级1995年~1999年竣工的为二级1980年~1994年竣工的为三级早于1980年竣工的为四级。选择房屋竣工日期后初始等级会自动填充。"
rules={[{ required: true, message: '请选择初始等级' }]}
>
<Tooltip title="初始等级无法手动更改由房屋详情的竣工日期决定2000年之后竣工的为一级1995年~1999年竣工的为二级1980年~1994年竣工的为三级早于1980年竣工的为四级。选择房屋竣工日期后初始等级会自动填充。">
<Radio.Group disabled buttonStyle="solid">
{codes.housePatrolInitGrade.map(item => {
return (
<Radio.Button key={item.code} value={+item.code}>
{item.value}
</Radio.Button>
)
})}
</Radio.Group>
</Tooltip>
<Radio.Group disabled buttonStyle="solid">
{codes.housePatrolInitGrade.map(item => {
return (
<Radio.Button key={item.code} value={+item.code}>
{item.value}
</Radio.Button>
)
})}
</Radio.Group>
</Form.Item>
<Form.Item

View File

@@ -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 (
<Form ref={this.form} className="yo-form">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
<div className="yo-form-group">
<Form.Item label="选择机构" name="grantOrgIdList">
<TreeSelect
showCheckedStrategy="SHOW_PARENT"
treeData={this.state.options.orgData}
placeholder="请选择机构"
treeCheckable
/>
</Form.Item>
<Form.Item label="选择区域" name="grantAreaCodeList">
<TreeSelect
showCheckedStrategy="SHOW_PARENT"
treeData={this.state.options.areaData}
placeholder="请选择所属区域"
treeCheckable
/>
</Form.Item>
</div>
</Spin>
</Form>
)
}
}

View File

@@ -1,11 +1,273 @@
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 (
<div>
1
</div>
<Form initialValues={initialValues} ref={this.form} className="yo-form">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
<div className="yo-form-group">
<Form.Item
label="所属组织机构"
name={['sysEmpParam', 'orgId']}
rules={[{ required: true, message: '所属组织机构' }]}
>
<TreeSelect
treeData={this.state.options.orgData}
dropdownStyle={{ maxHeight: '300px', overflow: 'auto' }}
treeDefaultExpandAll
placeholder="请选择所属组织机构"
onChange={value => this.onOrgChange(value)}
/>
</Form.Item>
<Form.Item
label="角色"
name="roleId"
rules={[{ required: true, message: '请选择角色' }]}
tooltip="片区内第一个用户必定为片区监管员,创建后不可更改角色"
>
<Select placeholder="请选择角色" disabled={this.state.lockRole}>
{this.state.options.roleData.map(item => {
return (
<Select.Option key={item.id} value={item.id}>
{item.name}
</Select.Option>
)
})}
</Select>
</Form.Item>
<Form.Item
label="账号"
name="account"
rules={[{ required: true, message: '请输入账号', trigger: 'blur' }]}
>
<Input autoComplete="off" placeholder="请输入账号" />
</Form.Item>
<Form.Item
label="姓名"
name="name"
rules={[{ required: true, message: '请输入姓名', trigger: 'blur' }]}
>
<Input autoComplete="off" placeholder="请输入姓名" />
</Form.Item>
{this.props.mode == 'add' && (
<>
<Form.Item
label="密码"
name="password"
rules={[
{ required: true, message: '请输入密码', trigger: 'blur' },
]}
>
<Input.Password autoComplete="off" placeholder="请输入密码" />
</Form.Item>
<Form.Item
label="确认密码"
name="confirm"
rules={[
{ required: true, message: '请确认密码', trigger: 'blur' },
]}
>
<Input.Password autoComplete="off" placeholder="请确认密码" />
</Form.Item>
</>
)}
<Form.Item label="昵称" name="nickName">
<Input autoComplete="off" placeholder="请输入昵称" />
</Form.Item>
<Form.Item label="生日" name="birthday">
<DatePicker className="w-100-p" />
</Form.Item>
<Form.Item label="性别" name="sex">
<Radio.Group>
<Radio.Button value={0}>
<AntIcon className="mr-xxs" type="stop" />
<span>保密</span>
</Radio.Button>
<Radio.Button value={1}>
<AntIcon
style={{ color: '#1890ff' }}
className="mr-xxs"
type="man"
/>
<span></span>
</Radio.Button>
<Radio.Button value={2}>
<AntIcon
style={{ color: '#eb2f96' }}
className="mr-xxs"
type="woman"
/>
<span></span>
</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label="邮箱" name="email">
<Input autoComplete="off" placeholder="请输入邮箱" />
</Form.Item>
<Form.Item label="手机号" name="phone">
<Input autoComplete="off" placeholder="请输入手机号" />
</Form.Item>
<Form.Item label="电话" name="tel">
<Input autoComplete="off" placeholder="请输入电话" />
</Form.Item>
</div>
</Spin>
</Form>
)
}
}

View File

@@ -13,13 +13,23 @@ import {
Switch,
Tag,
} from 'antd'
import { AntIcon, Auth, Container, Image, ModalForm, QueryList, QueryTreeLayout } from 'components'
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 = {
@@ -31,10 +41,12 @@ const apiAction = {
changeStatus: api.houseMemberChangeStatus,
resetPwd: api.sysUserResetPwd,
grantData: api.houseMemberGrantData,
}
// 用于弹窗标题
const name = '用户'
const name = '人员'
export default class index extends Component {
state = {
@@ -52,6 +64,7 @@ export default class index extends Component {
// 编辑窗口实例
editForm = React.createRef()
dataForm = React.createRef()
// 树选中节点
selectId = undefined
@@ -145,12 +158,12 @@ export default class index extends Component {
/**
* 打开新增/编辑弹窗
* @param {*} modal
* @param {*} record
* @param {*} id
*/
onOpen(modal, record) {
onOpen(modal, id) {
modal.current.open({
orgId: this.selectId,
record,
id,
})
}
@@ -173,33 +186,52 @@ export default class index extends Component {
/**
* 删除
* @param {*} record
* @param {*} id
*/
onDelete(record) {
this.onAction(apiAction.delete(record), '删除成功')
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 (
<List.Item
key={record.id}
key={id}
actions={[
<Auth auth="houseMember:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
</Auth>,
<Auth auth="houseMember:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
>
<a>删除</a>
</Popconfirm>
</Auth>,
<Auth aut="houseMember:resetPwd">
<a onClick={() => this.onResetPassword(record)}>重置密码</a>
</Auth>,
<QueryTableActions>
<Auth auth="houseMember:edit">
<a onClick={() => this.onOpen(this.editForm, id)}>编辑</a>
</Auth>
<Auth auth="houseMember:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(id)}
>
<a>删除</a>
</Popconfirm>
</Auth>
<Auth auth="houseMember:resetPwd">
<a onClick={() => this.onResetPassword(id)}>重置密码</a>
</Auth>
<Auth auth="houseMember:grantData">
<a onClick={() => this.onOpen(this.dataForm, id)}>授权额外数据</a>
</Auth>
</QueryTableActions>,
]}
>
<List.Item.Meta
@@ -209,15 +241,15 @@ export default class index extends Component {
type="avatar"
shape="square"
size={48}
id={record.avatar}
id={avatar}
icon={<AntIcon type="user" />}
/>
{record.roleCode && record.roleCode.includes('house_security_manager') && (
{roleCode && roleCode.includes('house_security_manager') && (
<Button
size="small"
type="primary"
className="block w-100-p mt-xxs"
onClick={() => this.onOpen(this.selectorModal, record)}
onClick={() => this.onOpen(this.selectorModal, id)}
>
选房
</Button>
@@ -226,9 +258,9 @@ export default class index extends Component {
}
title={
<>
{record.nickName || record.name}
{record.roleName &&
record.roleName.split(',').map((item, i) => (
{nickName || name}
{roleName &&
roleName.split(',').map((item, i) => (
<span key={i}>
<Divider type="vertical" />
<Tag color="pink">{item}</Tag>
@@ -236,25 +268,24 @@ export default class index extends Component {
))}
</>
}
description={record.account}
description={account}
/>
<Descriptions className="flex-1" column={2}>
<Descriptions.Item label="部门">{record.orgName}</Descriptions.Item>
<Descriptions.Item label="部门">{orgName}</Descriptions.Item>
<Descriptions.Item label="性别">
{this.bindCodeValue(record.sex, 'sex')}
{this.bindCodeValue(sex, 'sex')}
</Descriptions.Item>
<Descriptions.Item label="手机">{record.phone || '未设置'}</Descriptions.Item>
<Descriptions.Item label="邮箱">{record.email || '未设置'}</Descriptions.Item>
<Descriptions.Item label="手机">{phone || '未设置'}</Descriptions.Item>
<Descriptions.Item label="邮箱">{email || '未设置'}</Descriptions.Item>
</Descriptions>
<div className="yo-list-content--h">
<Auth auth="houseMember:changeStatus">
<div className="yo-list-content--h--item text-center">
<Switch
checked={!record.status}
loading={record.statusChanging}
checked={!status}
checkedChildren={this.bindCodeValue(0, 'common_status')}
unCheckedChildren={this.bindCodeValue(1, 'common_status')}
onChange={checked => this.onSetUserStatus(record, checked)}
onChange={checked => this.onSetUserStatus(id, checked)}
/>
</div>
</Auth>
@@ -263,18 +294,18 @@ export default class index extends Component {
)
}
onSetUserStatus(record, checked) {
onSetUserStatus(id, checked) {
this.onAction(
apiAction.changeStatus({
id: record.id,
id,
status: +!checked,
}),
'设置成功'
)
}
onResetPassword(record) {
this.onAction(apiAction.resetPwd(record), '重置成功')
onResetPassword(id) {
this.onAction(apiAction.resetPwd({ id }), '重置成功')
}
//#endregion
@@ -349,6 +380,15 @@ export default class index extends Component {
<FormBody mode="edit" />
</ModalForm>
<ModalForm
title="数据授权"
action={apiAction.grantData}
ref={this.dataForm}
onSuccess={() => this.list.current.onReloadData()}
>
<DataForm />
</ModalForm>
<ModalForm
bodyStyle={{ padding: 0 }}
footer={false}

View File

@@ -26,7 +26,7 @@ export default class index extends Component {
*/
async fillData(params) {
this.setState({
userId: params.record.id,
userId: params.id,
})
}

View File

@@ -87,7 +87,8 @@ export default class form extends Component {
this.record = {
pid: params.pid,
...this.record,
areaCode: areaCode.length == 4 ? areaCode : [],
areaCode:
areaCode.length > 0 && areaCode[areaCode.length - 1].length == 12 ? areaCode : [],
}
//#endregion
@@ -213,6 +214,7 @@ export default class form extends Component {
rules={[{ required: true, message: '请选择所属区域' }]}
>
<Cascader
disabled={this.state.exist}
options={this.state.options.areaData}
fieldNames={{
label: 'name',

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react'
import { Button, Card, Form, Input, message as Message, Popconfirm } from 'antd'
import { Button, Radio, Card, Form, Input, message as Message, Popconfirm } from 'antd'
import {
AntIcon,
Auth,
@@ -31,6 +31,7 @@ export default class index extends Component {
codes: {
houseType: [],
},
type: 1,
}
// 表格实例
@@ -246,8 +247,27 @@ export default class index extends Component {
autoLoad={false}
loadData={this.loadData}
columns={this.columns}
queryInitialValues={{
type: 1,
}}
onQueryChange={values => {
if (values.hasOwnProperty('type')) {
this.setState({ type: values.type })
}
}}
query={
<Auth auth="houseProjectInfo:page">
<Form.Item label="项目类型" name="type">
<Radio.Group buttonStyle="solid">
<Radio.Button value={0}>全部</Radio.Button>
<Radio.Button value={1}>
<span>住宅</span>
</Radio.Button>
<Radio.Button value={2}>
<span>非住宅</span>
</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label="项目名称" name="name">
<Input autoComplete="off" placeholder="请输入项目名称" />
</Form.Item>

View File

@@ -0,0 +1,538 @@
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 } 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: {
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')}`}
<br />
<Tag color="purple">{text}</Tag>
</>
),
},
{
title: '房屋性质及行业',
dataIndex: 'type',
sorter: true,
width: 150,
render: text => this.bindCodeValue(text, 'house_type'),
},
{
title: '地址',
dataIndex: 'address',
sorter: true,
},
{
title: '任务截止时间',
dataIndex: 'endTime',
sorter: true,
width: 150,
},
]
/**
* 构造函数,在渲染前动态添加操作字段等
* @param {*} props
*/
constructor(props) {
super(props)
const flag = auth({ [authName]: [['edit'], ['delete']] })
}
/**
* 阻止外部组件引发的渲染,提升性能
* 可自行添加渲染条件
* [必要]
* @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_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(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 自定义方法
rednerMoreQuery() {
const { codes, showDrawingMaterialText } = this.state
return (
<Row gutter={16}>
<Col span={24}>
<Form.Item label="使用状态" name="houseUsedStatus">
<Checkbox.Group>
{codes.houseUsedStatus.map(item => (
<Checkbox key={item.code} value={+item.code}>
{item.value}
</Checkbox>
))}
</Checkbox.Group>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="有无幕墙" name="curtainWall">
<Checkbox.Group>
<Checkbox value="">全部</Checkbox>
<Checkbox value={0}></Checkbox>
<Checkbox value={1}></Checkbox>
</Checkbox.Group>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="有无面砖" name="faceBrick">
<Checkbox.Group>
<Checkbox value="">全部</Checkbox>
<Checkbox value={0}></Checkbox>
<Checkbox value={1}></Checkbox>
</Checkbox.Group>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="有无涂料" name="coating">
<Checkbox.Group>
<Checkbox value="">全部</Checkbox>
<Checkbox value={0}></Checkbox>
<Checkbox value={1}></Checkbox>
</Checkbox.Group>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="有无粉刷" name="painting">
<Checkbox.Group>
<Checkbox value="">全部</Checkbox>
<Checkbox value={0}></Checkbox>
<Checkbox value={1}></Checkbox>
</Checkbox.Group>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="安全管理员">
<Input autoComplete="off" placeholder="请输入安全管理员" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="片区监管员">
<Input autoComplete="off" placeholder="请输入片区监管员" />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item label="产权性质" name="propertyRights">
<Checkbox.Group>
<Checkbox value="">全部</Checkbox>
{codes.housePropertyRights.map(item => (
<Checkbox key={item.code} value={+item.code}>
{item.value}
</Checkbox>
))}
</Checkbox.Group>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="土地性质" name="landAttribute">
<Checkbox.Group>
<Checkbox value="">全部</Checkbox>
{codes.landAttribute.map(item => (
<Checkbox key={item.code} value={+item.code}>
{item.value}
</Checkbox>
))}
</Checkbox.Group>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="基础情况" name="baseInfo">
<Checkbox.Group>
<Checkbox value="">全部</Checkbox>
{codes.houseBaseInfo.map(item => (
<Checkbox key={item.code} value={+item.code}>
{item.value}
</Checkbox>
))}
</Checkbox.Group>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item label="结构类型" name="structureType">
<Checkbox.Group>
<Checkbox value="">全部</Checkbox>
{codes.houseStructureType.map(item => (
<Checkbox key={item.code} value={+item.code}>
{item.value}
</Checkbox>
))}
</Checkbox.Group>
</Form.Item>
</Col>
<Col span={24}>
<Form.Item label="图纸资料存档处" name="drawingMaterial">
<Form.Item name="drawingMaterial" className="mb-none">
<Checkbox.Group>
{codes.houseStorageOfDrawings.map(item => (
<Checkbox key={item.code} value={item.code}>
{item.value}
</Checkbox>
))}
</Checkbox.Group>
</Form.Item>
{showDrawingMaterialText && (
<Form.Item name="drawingMaterialText" className="mb-none mt-xs">
<Input.TextArea autoSize placeholder="请输入其他图纸资料存档处" />
</Form.Item>
)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="竣工日期" name="completedDate">
<DatePicker.RangePicker
className="w-100-p"
placeholder={['请选择竣工开始日期', '请选择竣工结束日期']}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="填表日期" name="createdTime">
<DatePicker.RangePicker
className="w-100-p"
placeholder={['请选择填表开始日期', '请选择填表结束日期']}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="总建筑面积" name="totalArea">
<InputNumberRange
min={0}
unit="m²"
placeholder={['请输入最小总建筑面积', '请输入最大总建筑面积']}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="总层数" name="totalFloor">
<InputNumberRange
min={0}
precision={0}
step={1}
unit="层"
placeholder={['请输入最小总层数', '请输入最大总层数']}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="建设单位" name="buildingUnit">
<Input autoComplete="off" placeholder="请输入建设单位" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="设计单位" name="desingerUnit">
<Input autoComplete="off" placeholder="请输入设计单位" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="施工单位" name="constructionUnit">
<Input autoComplete="off" placeholder="请输入施工单位" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="监理单位" name="monitorUnit">
<Input autoComplete="off" placeholder="请输入监理单位" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="产权单位" name="propertyUnit">
<Input autoComplete="off" placeholder="请输入产权单位" />
</Form.Item>
</Col>
<Col span={24}>
<Form.Item label="综合等级" name="houseGrade">
<Checkbox.Group>
<Checkbox value="">全部</Checkbox>
{codes.houseGrade.map(item => (
<Checkbox key={item.code} value={+item.code}>
{item.value}
</Checkbox>
))}
</Checkbox.Group>
</Form.Item>
</Col>
</Row>
)
}
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 (
<Container mode="fluid">
<br />
<Card bordered={false}>
<QueryTable
ref={this.table}
autoLoad={false}
loadData={this.loadData}
columns={this.columns}
queryInitialValues={{
houseUsedStatus: [1, 2],
curtainWall: [''],
faceBrick: [''],
coating: [''],
painting: [''],
propertyRights: [''],
landAttribute: [''],
baseInfo: [''],
structureType: [''],
houseGrade: [''],
}}
query={
<Auth auth={{ [authName]: 'page' }}>
<Form.Item label="地址" name="address">
<Input autoComplete="off" placeholder="请输入地址" />
</Form.Item>
<Form.Item label="房屋唯一编码" name="houseCode">
<Input autoComplete="off" placeholder="请输入房屋唯一编码" />
</Form.Item>
</Auth>
}
moreQuery={
<Auth auth={{ [authName]: 'page' }}>{this.rednerMoreQuery()}</Auth>
}
onQueryChange={(changedValues, allValues) =>
this.onQueryChange(changedValues, allValues)
}
operator={
<Auth auth={{ [authName]: 'add' }}>
<Button
icon={<AntIcon type="plus" />}
onClick={() => this.onOpen(this.addForm)}
>
新增{name}
</Button>
</Auth>
}
/>
</Card>
</Container>
)
}
}

View File

@@ -0,0 +1,304 @@
import React, { Component } from 'react'
import { Button, Card, Form, Input, message as Message, Popconfirm, Radio, Select, Tag } 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 { 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: '',
}
// 表格实例
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')}`}
<br />
<Tag color="purple">{text}</Tag>
</>
),
},
{
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) => (
<QueryTableActions>
<Auth auth={{ houseInfo: 'getByTaskId' }}>
<a onClick={() => this.onOpen(record.id)}>
{record.state === 3 ? `审核` : `查看`}
</a>
</Auth>
</QueryTableActions>
),
})
}
}
/**
* 阻止外部组件引发的渲染,提升性能
* 可自行添加渲染条件
* [必要]
* @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 自定义方法
//#endregion
render() {
const { codes, type } = this.state
return (
<Container mode="fluid">
<br />
<Card bordered={false}>
<QueryTable
ref={this.table}
autoLoad={false}
loadData={this.loadData}
columns={this.columns}
queryInitialValues={{
type: '',
status: '',
}}
onQueryChange={values => {
if (values.hasOwnProperty('type')) {
this.setState({ type: values.type })
}
}}
query={
<Auth auth={{ [authName]: 'page' }}>
<Form.Item label="房屋性质" name="type">
<Radio.Group buttonStyle="solid">
<Radio.Button value="">全部</Radio.Button>
{codes.houseType.map(item => (
<Radio.Button key={item.code} value={item.code}>
{item.value}
</Radio.Button>
))}
</Radio.Group>
</Form.Item>
{type == 2 && (
<Form.Item label="行业" name="industry">
<Select
allowClear
className="w-150"
placeholder="请选择行业"
>
{codes.houseIndustry.map(item => (
<Select.Option key={item.code} value={item.code}>
{item.value}
</Select.Option>
))}
</Select>
</Form.Item>
)}
<Form.Item label="地址" name="address">
<Input autoComplete="off" placeholder="请输入地址" />
</Form.Item>
<Form.Item label="房屋唯一编码" name="houseCode">
<Input autoComplete="off" placeholder="请输入房屋唯一编码" />
</Form.Item>
<Form.Item label="审核状态" name="status">
<Radio.Group buttonStyle="solid">
<Radio.Button value="">全部</Radio.Button>
{codes.status.map(item => (
<Radio.Button key={item.code} value={item.code}>
{item.value}
</Radio.Button>
))}
</Radio.Group>
</Form.Item>
</Auth>
}
/>
</Card>
</Container>
)
}
}

View File

@@ -1,11 +1,12 @@
import React, { Component } from 'react'
import { Button, Card, Form, Input, message as Message, Popconfirm, Radio, Select, Tag } from 'antd'
import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components'
import { Card, Form, Input, message as Message, Radio, Select, Tag } from 'antd'
import { Auth, Container, 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'
/**
* 注释段[\/**\/]为必须要改
@@ -27,6 +28,14 @@ const authName = 'houseTask'
export default class index extends Component {
state = {
codes: {
status: [
{ code: -1, value: '审核退回' },
{ code: 0, value: '待处理' },
{ code: 1, value: '暂存' },
{ code: 2, value: '待提交' },
{ code: 3, value: '审核中' },
{ code: 6, value: '审核通过' },
],
houseType: [],
houseIndustry: [],
},
@@ -63,7 +72,9 @@ export default class index extends Component {
dataIndex: 'type',
sorter: true,
width: 150,
render: text => this.bindCodeValue(text, 'house_type'),
render: (text, record) =>
this.bindCodeValue(text, 'house_type') +
(text === 2 ? `(${this.bindCodeValue(record.industry, 'house_industry')})` : ''),
},
{
title: '地址',
@@ -76,6 +87,13 @@ export default class index extends Component {
sorter: true,
width: 150,
},
{
title: '建档状态',
dataIndex: 'state',
sorter: true,
width: 100,
render: text => this.bindCodeValue(text, 'status'),
},
]
/**
@@ -95,7 +113,13 @@ export default class index extends Component {
render: (text, record) => (
<QueryTableActions>
<Auth auth={{ houseInfo: 'getByTaskId' }}>
<a onClick={() => this.onOpen(record.id)}>登记</a>
<a onClick={() => this.onOpen(record.id)}>
{record.state === -1 || record.state === 1 || record.state === 2
? `修改`
: record.state === 3 || record.state === 6
? `查看`
: `登记`}
</a>
</Auth>
</QueryTableActions>
),
@@ -123,7 +147,7 @@ export default class index extends Component {
const { onLoading, onLoadData } = this.table.current
onLoading()
getDictData('house_type', 'house_industry').then(codes => {
this.setState({ codes }, () => {
this.setState({ codes: { ...this.state.codes, ...codes } }, () => {
onLoadData()
})
})
@@ -137,9 +161,20 @@ export default class index extends Component {
* @returns
*/
loadData = async (params, query) => {
const searchInfo = getSearchInfo({
query,
queryType: {
type: '=',
industry: '=',
address: 'like',
houseCode: 'like',
state: '=',
},
})
const { data } = await apiAction.page({
...params,
...query,
searchInfo,
})
return data
}
@@ -173,6 +208,7 @@ export default class index extends Component {
path: 'business/house/info/form',
param: {
taskId,
table: this.table,
},
})
}
@@ -216,6 +252,7 @@ export default class index extends Component {
columns={this.columns}
queryInitialValues={{
type: '',
state: 0,
}}
onQueryChange={values => {
if (values.hasOwnProperty('type')) {
@@ -255,6 +292,16 @@ export default class index extends Component {
<Form.Item label="房屋唯一编码" name="houseCode">
<Input autoComplete="off" placeholder="请输入房屋唯一编码" />
</Form.Item>
<Form.Item label="建档状态" name="state">
<Select allowClear className="w-150" placeholder="建档状态">
<Select.Option value="">全部</Select.Option>
{codes.status.map(item => (
<Select.Option key={item.code} value={item.code}>
{item.value}
</Select.Option>
))}
</Select>
</Form.Item>
</Auth>
}
/>

View File

@@ -1,14 +1,13 @@
import React, { Component } from 'react'
import { Form, Input, InputNumber, Spin } from 'antd'
import { AntIcon, IconSelector } from 'components'
import { cloneDeep } from 'lodash'
import { AntIcon, ColorSelector, IconSelector } from 'components'
import { api } from 'common/api'
const initialValues = {
sort: 100
sort: 100,
}
export default class form extends Component {
state = {
// 加载状态
loading: true,
@@ -33,17 +32,18 @@ export default class form extends Component {
* 填充数据
* 可以在设置this.record之后对其作出数据结构调整
* [异步,必要]
* @param {*} params
* @param {*} params
*/
async fillData(params) {
this.record = cloneDeep(params.record)
//#region 从后端转换成前段所需格式
if (params.id) {
this.record = (await api.sysAppDetail({ id: params.id })).data
}
//#endregion
this.form.current.setFieldsValue(this.record)
this.setState({
loading: false
loading: false,
})
}
@@ -51,7 +51,7 @@ export default class form extends Component {
* 获取数据
* 可以对postData进行数据结构调整
* [异步,必要]
* @returns
* @returns
*/
async getData() {
const form = this.form.current
@@ -73,17 +73,21 @@ export default class form extends Component {
render() {
return (
<Form
initialValues={initialValues}
ref={this.form}
className="yo-form"
>
<Form initialValues={initialValues} ref={this.form} className="yo-form">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
<div className="yo-form-group">
<Form.Item label="应用名称" name="name" rules={[{ required: true, message: '请输入应用名称' }]}>
<Form.Item
label="应用名称"
name="name"
rules={[{ required: true, message: '请输入应用名称' }]}
>
<Input autoComplete="off" placeholder="请输入应用名称" />
</Form.Item>
<Form.Item label="唯一编码" name="code" rules={[{ required: true, message: '请输入唯一编码' }]}>
<Form.Item
label="唯一编码"
name="code"
rules={[{ required: true, message: '请输入唯一编码' }]}
>
<Input autoComplete="off" placeholder="请输入唯一编码" />
</Form.Item>
<Form.Item label="图标" name="icon">
@@ -94,15 +98,17 @@ export default class form extends Component {
<AntIcon
type="setting"
onClick={() =>
this
.iconSelector
.current
.open(this.form.current.getFieldValue('icon'))
this.iconSelector.current.open(
this.form.current.getFieldValue('icon')
)
}
/>
}
/>
</Form.Item>
<Form.Item label="颜色" name="color">
<ColorSelector placeholder="请选择颜色" />
</Form.Item>
<Form.Item label="排序" name="sort">
<InputNumber
max={1000}
@@ -113,9 +119,14 @@ export default class form extends Component {
</Form.Item>
</div>
</Spin>
<IconSelector ref={this.iconSelector} onSelect={(icon) => this.form.current.setFieldsValue({
icon
})} />
<IconSelector
ref={this.iconSelector}
onSelect={icon =>
this.form.current.setFieldsValue({
icon,
})
}
/>
</Form>
)
}

View File

@@ -38,6 +38,33 @@ export default class index extends Component {
// 表格字段
columns = [
{
title: '图标',
dataIndex: 'icon',
width: 32,
render: (text, record) => (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '32px',
height: '32px',
borderRadius: '100%',
backgroundColor: record.color,
margin: '0 auto',
}}
>
<AntIcon
type={text}
style={{
fontSize: '20px',
color: '#fff',
}}
/>
</div>
),
},
{
title: '应用名称',
dataIndex: 'name',
@@ -87,6 +114,7 @@ export default class index extends Component {
dataIndex: 'sort',
width: 100,
sorter: true,
defaultSortOrder: 'ascend',
},
]
@@ -107,7 +135,7 @@ export default class index extends Component {
render: (text, record) => (
<QueryTableActions>
<Auth auth="sysApp:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
<a onClick={() => this.onOpen(this.editForm, record.id)}>编辑</a>
</Auth>
<Auth auth="sysApp:delete">
<Popconfirm
@@ -191,11 +219,11 @@ export default class index extends Component {
/**
* 打开新增/编辑弹窗
* @param {*} modal
* @param {*} record
* @param {*} id
*/
onOpen(modal, record) {
onOpen(modal, id) {
modal.current.open({
record,
id,
})
}

View File

@@ -48,7 +48,7 @@ export default class index extends Component {
title: '区域类型',
dataIndex: 'levelType',
sorter: true,
width: 50,
width: 100,
render: text => <>{this.bindCodeValue(text, 'areacode_type')}</>,
},
{
@@ -60,14 +60,15 @@ export default class index extends Component {
{
title: '区域编号',
dataIndex: 'code',
width: 80,
width: 100,
sorter: true,
},
{
title: '行政编号',
dataIndex: 'adCode',
width: 80,
width: 100,
sorter: true,
defaultSortOrder: 'ascend',
},
{
title: '描述',

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react'
import { Button, Card, Form, Input, message as Message, Popconfirm } from 'antd'
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'
@@ -41,28 +41,41 @@ export default class index extends Component {
{
title: '参数名称',
dataIndex: 'name',
width: 200,
sorter: true,
},
{
title: '唯一编码',
dataIndex: 'code',
width: 200,
sorter: true,
ellipsis: {
showTitle: false,
},
render: text => <Tooltip title={text}>{text}</Tooltip>,
},
{
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 => <Tooltip title={text}>{text}</Tooltip>,
},
]
@@ -213,6 +226,7 @@ export default class index extends Component {
autoLoad={false}
loadData={this.loadData}
columns={this.columns}
scroll={{ x: 1140 }}
query={
<Auth auth={{ [authName]: 'page' }}>
<Form.Item label="参数名称" name="name">

View File

@@ -14,20 +14,19 @@ const apiAction = {
add: api.sysDictDataAdd,
edit: api.sysDictDataEdit,
delete: api.sysDictDataDelete,
deleteBatch: api.sysDictDataDeleteBatch
deleteBatch: api.sysDictDataDeleteBatch,
}
// 用于弹窗标题
const name = '字典值'
export default class index extends Component {
state = {
codes: {
commonStatus: []
commonStatus: [],
},
selectedRowKeys: []
selectedRowKeys: [],
}
// 表格实例
@@ -45,7 +44,7 @@ export default class index extends Component {
dataIndex: 'value',
sorter: true,
width: 200,
render: (text, record, index) =>
render: (text, record, index) => (
<Form.Item
name={[index, 'value']}
rules={[{ required: true, message: '请输入文本' }]}
@@ -53,13 +52,14 @@ export default class index extends Component {
>
<Input autoComplete="off" placeholder="请输入文本" />
</Form.Item>
),
},
{
title: '字典值',
dataIndex: 'code',
sorter: true,
width: 200,
render: (text, record, index) =>
render: (text, record, index) => (
<Form.Item
name={[index, 'code']}
rules={[{ required: true, message: '请输入文本' }]}
@@ -67,18 +67,19 @@ export default class index extends Component {
>
<Input autoComplete="off" placeholder="请输入字典值" />
</Form.Item>
),
},
{
title: '扩展值',
dataIndex: 'extCode',
width: 80,
align: 'center',
render: (text, record, index) =>
render: (text, record, index) => (
<>
<Form.Item name={[index, 'extCode']} className="hidden">
<Input type="hidden" />
</Form.Item>
{auth('sysDictData:edit') ?
{auth('sysDictData:edit') ? (
<a
onClick={() => this.onOpen(this.jsonForm, record)}
style={{
@@ -87,50 +88,58 @@ export default class index extends Component {
transform: 'scaleY(.85)',
color: 'transparent',
backgroundImage: 'linear-gradient(135deg, #007bff, #52c41a)',
WebkitBackgroundClip: 'text'
WebkitBackgroundClip: 'text',
}}
>JSON</a>
:
>
JSON
</a>
) : (
<>{text}</>
}
)}
</>
),
},
{
title: '排序',
dataIndex: 'sort',
sorter: true,
width: 100,
render: (text, record, index) => <Form.Item name={[index, 'sort']} className="mb-none">
<InputNumber
max={1000}
min={0}
step={1}
className="w-100-p"
autoComplete="off"
placeholder="排序"
/>
</Form.Item>
render: (text, record, index) => (
<Form.Item name={[index, 'sort']} className="mb-none">
<InputNumber
max={1000}
min={0}
step={1}
className="w-100-p"
autoComplete="off"
placeholder="排序"
/>
</Form.Item>
),
defaultSortOrder: 'ascend',
},
{
title: '备注',
dataIndex: 'remark',
sorter: true,
render: (text, record, index) => <Form.Item name={[index, 'remark']} className="mb-none">
<Input autoComplete="off" placeholder="请输入备注" />
</Form.Item>
render: (text, record, index) => (
<Form.Item name={[index, 'remark']} className="mb-none">
<Input autoComplete="off" placeholder="请输入备注" />
</Form.Item>
),
},
{
title: '状态',
dataIndex: 'status',
sorter: true,
width: 80,
render: text => this.bindCodeValue(text, 'common_status')
}
render: text => this.bindCodeValue(text, 'common_status'),
},
]
/**
* 构造函数,在渲染前动态添加操作字段等
* @param {*} props
* @param {*} props
*/
constructor(props) {
super(props)
@@ -142,27 +151,28 @@ export default class index extends Component {
title: '操作',
width: 150,
dataIndex: 'actions',
render: (text, record, index) => (<QueryTableActions>
{
record.id !== -1 ?
render: (text, record, index) => (
<QueryTableActions>
{record.id !== -1 ? (
<Auth auth="sysDictData:edit">
<a onClick={() => this.onEdit(index)}>保存编辑</a>
</Auth>
:
) : (
<Auth auth="sysDictData:add">
<a onClick={() => this.onAdd(index)}>保存新增</a>
</Auth>
}
<Auth auth="sysDictData:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
>
<a>删除</a>
</Popconfirm>
</Auth>
</QueryTableActions>)
)}
<Auth auth="sysDictData:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
>
<a>删除</a>
</Popconfirm>
</Auth>
</QueryTableActions>
),
})
}
}
@@ -171,9 +181,9 @@ export default class index extends Component {
* 阻止外部组件引发的渲染,提升性能
* 可自行添加渲染条件
* [必要]
* @param {*} props
* @param {*} state
* @returns
* @param {*} props
* @param {*} state
* @returns
*/
shouldComponentUpdate(props, state) {
return !isEqual(this.state, state)
@@ -186,26 +196,28 @@ export default class index extends Component {
componentDidMount() {
this.table.current.onLoading()
getDictData('common_status').then(res => {
this.setState({
codes: res
}, () => {
this.table.current.onLoadData()
})
this.setState(
{
codes: res,
},
() => {
this.table.current.onLoadData()
}
)
})
}
/**
* 调用加载数据接口,可在调用前对query进行处理
* [异步,必要]
* @param {*} params
* @param {*} query
* @returns
* @param {*} params
* @param {*} query
* @returns
*/
loadData = async (params, query) => {
query = {
...query,
typeId: this.props.type.id
typeId: this.props.type.id,
}
const { data } = await apiAction.page({
@@ -225,15 +237,15 @@ export default class index extends Component {
/**
* 绑定字典数据
* @param {*} code
* @param {*} name
* @returns
* @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)
const c = codes.find(p => p.code == code)
if (c) {
return c.value
}
@@ -243,20 +255,20 @@ export default class index extends Component {
/**
* 打开新增/编辑弹窗
* @param {*} modal
* @param {*} record
* @param {*} modal
* @param {*} record
*/
onOpen(modal, record) {
modal.current.open({
record
record,
})
}
/**
* 对表格上的操作进行统一处理
* [异步]
* @param {*} action
* @param {*} successMessage
* @param {*} action
* @param {*} successMessage
*/
async onAction(action, successMessage, reload = true) {
const table = this.table.current
@@ -276,13 +288,10 @@ export default class index extends Component {
/**
* 删除
* @param {*} record
* @param {*} record
*/
onDelete(record) {
this.onAction(
apiAction.delete(record),
'删除成功'
)
this.onAction(apiAction.delete(record), '删除成功')
}
//#region 自定义方法
@@ -295,12 +304,12 @@ export default class index extends Component {
typeId: this.props.type.id,
sort: 100,
status: 0,
remark: null
remark: null,
}
const index = this.table.current.onAddRow(record)
if (index !== false) {
this.form.current.setFieldsValue({
[index]: record
[index]: record,
})
}
}
@@ -318,10 +327,7 @@ export default class index extends Component {
const record = form.getFieldsValue([index])[index]
// 为了正常显示checkbox,默认给id赋予了-1,在这里删除id以表示新增
record.id = undefined
this.onAction(
apiAction.add(record),
'新增成功'
)
this.onAction(apiAction.add(record), '新增成功')
}
async onEdit(index) {
@@ -335,21 +341,14 @@ export default class index extends Component {
}
}
const record = form.getFieldsValue([index])[index]
this.onAction(
apiAction.edit(record),
'编辑成功',
false
)
this.onAction(apiAction.edit(record), '编辑成功', false)
}
async onDeleteBatch() {
await this.onAction(
apiAction.deleteBatch(this.state.selectedRowKeys),
'删除成功'
)
await this.onAction(apiAction.deleteBatch(this.state.selectedRowKeys), '删除成功')
this.setState({
selectedRowKeys: []
selectedRowKeys: [],
})
}
@@ -360,8 +359,8 @@ export default class index extends Component {
index = dataSource.indexOf(data)
this.form.current.setFieldsValue({
[index]: {
extCode
}
extCode,
},
})
dataSource[index].extCode = extCode
table.setState({ dataSource })
@@ -369,7 +368,6 @@ export default class index extends Component {
//#endregion
render() {
const { selectedRowKeys } = this.state
return (
@@ -387,9 +385,9 @@ export default class index extends Component {
rowSelection={{
selectedRowKeys,
onChange: selectedRowKeys => this.setState({ selectedRowKeys }),
getCheckboxProps: (record) => ({
disabled: record.id === -1
})
getCheckboxProps: record => ({
disabled: record.id === -1,
}),
}}
query={
<Auth auth="sysDictData:page">
@@ -409,28 +407,27 @@ export default class index extends Component {
title="是否确认批量删除"
onConfirm={() => this.onDeleteBatch()}
>
<Button disabled={!selectedRowKeys.length} danger>批量删除</Button>
<Button disabled={!selectedRowKeys.length} danger>
批量删除
</Button>
</Popconfirm>
</Auth>
}
footer={
() =>
<Auth auth="sysDictData:add">
<Button
block
icon={<AntIcon type="plus" />}
onClick={() => this.onAddRow()}
>新增{name}</Button>
</Auth>
}
footer={() => (
<Auth auth="sysDictData:add">
<Button
block
icon={<AntIcon type="plus" />}
onClick={() => this.onAddRow()}
>
新增{name}
</Button>
</Auth>
)}
/>
</Card>
<ModalForm
title="编辑"
action={this.onSaveExtCode}
ref={this.jsonForm}
>
<ModalForm title="编辑" action={this.onSaveExtCode} ref={this.jsonForm}>
<FormBody />
</ModalForm>
</Container>

View File

@@ -1,6 +1,14 @@
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 {
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'
@@ -14,17 +22,16 @@ const apiAction = {
page: api.sysDictTypePage,
add: api.sysDictTypeAdd,
edit: api.sysDictTypeEdit,
delete: api.sysDictTypeDelete
delete: api.sysDictTypeDelete,
}
const name = '字典'
export default class index extends Component {
state = {
codes: {
commonStatus: []
}
commonStatus: [],
},
}
// 表格实例
@@ -43,24 +50,29 @@ export default class index extends Component {
{
title: '字典名称',
dataIndex: 'name',
width: 200,
sorter: true,
},
{
title: '类型',
key: 'type',
dataIndex: 'code',
width: 120,
sorter: true,
render: text => text ? '字典类型' : '目录'
render: text => (text ? '字典类型' : '目录'),
},
{
title: '唯一编码',
dataIndex: 'code',
width: 120,
sorter: true,
},
{
title: '排序',
dataIndex: 'sort',
width: 80,
sorter: true,
defaultSortOrder: 'ascend',
},
{
title: '备注',
@@ -71,14 +83,15 @@ export default class index extends Component {
{
title: '状态',
dataIndex: 'status',
width: 80,
sorter: true,
render: text => this.bindCodeValue(text, 'common_status')
render: text => this.bindCodeValue(text, 'common_status'),
},
]
/**
* 构造函数,在渲染前动态添加操作字段等
* @param {*} props
* @param {*} props
*/
constructor(props) {
super(props)
@@ -90,20 +103,22 @@ export default class index extends Component {
title: '操作',
width: 150,
dataIndex: 'actions',
render: (text, record) => (<QueryTableActions>
<Auth auth="sysDict:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
</Auth>
<Auth auth="sysDict:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
>
<a>删除</a>
</Popconfirm>
</Auth>
</QueryTableActions>)
render: (text, record) => (
<QueryTableActions>
<Auth auth="sysDict:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
</Auth>
<Auth auth="sysDict:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
>
<a>删除</a>
</Popconfirm>
</Auth>
</QueryTableActions>
),
})
}
}
@@ -112,9 +127,9 @@ export default class index extends Component {
* 阻止外部组件引发的渲染,提升性能
* 可自行添加渲染条件
* [必要]
* @param {*} props
* @param {*} state
* @returns
* @param {*} props
* @param {*} state
* @returns
*/
shouldComponentUpdate(props, state) {
return !isEqual(this.state, state)
@@ -127,26 +142,28 @@ export default class index extends Component {
componentDidMount() {
this.table.current.onLoading()
getDictData('common_status').then(res => {
this.setState({
codes: res
}, () => {
this.table.current.onLoadData()
})
this.setState(
{
codes: res,
},
() => {
this.table.current.onLoadData()
}
)
})
}
/**
* 调用加载数据接口,可在调用前对query进行处理
* [异步,必要]
* @param {*} params
* @param {*} query
* @returns
*/
* 调用加载数据接口,可在调用前对query进行处理
* [异步,必要]
* @param {*} params
* @param {*} query
* @returns
*/
loadData = async (params, query) => {
query = {
...query,
pid: this.selectId
pid: this.selectId,
}
const { data } = await apiAction.page({
@@ -157,10 +174,10 @@ export default class index extends Component {
}
/**
* 调用树结构数据接口
* [异步,必要]
* @returns
*/
* 调用树结构数据接口
* [异步,必要]
* @returns
*/
loadTreeData = async () => {
const { data } = await apiAction.tree()
return data
@@ -169,7 +186,7 @@ export default class index extends Component {
/**
* 树节点选中事件
* [必要]
* @param {*} id
* @param {*} id
*/
onSelectTree(id) {
this.selectId = id
@@ -178,15 +195,15 @@ export default class index extends Component {
/**
* 绑定字典数据
* @param {*} code
* @param {*} name
* @returns
* @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)
const c = codes.find(p => p.code == code)
if (c) {
return c.value
}
@@ -196,21 +213,21 @@ export default class index extends Component {
/**
* 打开新增/编辑弹窗
* @param {*} modal
* @param {*} record
* @param {*} modal
* @param {*} record
*/
onOpen(modal, record) {
modal.current.open({
pid: this.selectId,
record
record,
})
}
/**
* 对表格上的操作进行统一处理
* [异步]
* @param {*} action
* @param {*} successMessage
* @param {*} action
* @param {*} successMessage
*/
async onAction(action, successMessage) {
this.table.current.onLoading()
@@ -225,13 +242,10 @@ export default class index extends Component {
/**
* 删除
* @param {*} record
* @param {*} record
*/
onDelete(record) {
this.onAction(
apiAction.delete(record),
'删除成功'
)
this.onAction(apiAction.delete(record), '删除成功')
}
//#region 自定义方法
@@ -242,7 +256,7 @@ export default class index extends Component {
<QueryTreeLayout
loadData={this.loadTreeData}
defaultExpanded={true}
onSelect={(key) => this.onSelectTree(key)}
onSelect={key => this.onSelectTree(key)}
>
<Container mode="fluid">
<Card bordered={false}>
@@ -252,7 +266,7 @@ export default class index extends Component {
loadData={this.loadData}
columns={this.columns}
queryInitialValues={{
type: 2
type: 2,
}}
query={
<Auth auth="sysDict:page">
@@ -275,12 +289,14 @@ export default class index extends Component {
<Button
icon={<AntIcon type="plus" />}
onClick={() => this.onOpen(this.addForm)}
>新增{name}</Button>
>
新增{name}
</Button>
</Auth>
}
expandable={{
expandedRowRender: record => <DictData type={record} />,
rowExpandable: record => !!record.code
rowExpandable: record => !!record.code,
}}
/>
</Card>

View File

@@ -1,23 +1,42 @@
import React, { Component } from 'react'
import { Alert, Button, Card, Descriptions, Form, Popconfirm, Input, message as Message, Select, DatePicker } from 'antd'
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 { RangePicker } = DatePicker
const apiAction = {
page: api.sysOpLogPage,
delete: api.sysOpLogDelete
delete: api.sysOpLogDelete,
}
const methodColor = {
POST: 'orange',
GET: 'green',
}
export default class index extends Component {
state = {
codes: {
opType: []
}
opType: [],
},
}
// 表格实例
table = React.createRef()
@@ -26,37 +45,54 @@ export default class index extends Component {
{
title: '日志名称',
dataIndex: 'name',
sorter: true,
},
{
title: '操作类型',
dataIndex: 'opType',
render: text => (<>{this.bindCodeValue(text, 'op_type')}</>),
sorter: true,
},
{
title: '是否成功',
dataIndex: 'success',
render: text => (<> {text ? '是' : '否'}</>),
sorter: true,
},
{
title: 'ip',
dataIndex: 'ip',
width: 200,
sorter: true,
},
{
title: '请求地址',
dataIndex: 'url',
width: 300,
sorter: true,
render: (text, record) => (
<>
<Tag color={methodColor[record.reqMethod.toUpperCase()]}>
{record.reqMethod}
</Tag>{' '}
{text}
</>
),
},
{
title: '是否成功',
dataIndex: 'success',
width: 100,
render: text => (
<>
{text ? (
<span className="text-success"></span>
) : (
<span className="text-error"></span>
)}
</>
),
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,
},
@@ -66,9 +102,9 @@ export default class index extends Component {
* 阻止外部组件引发的渲染,提升性能
* 可自行添加渲染条件
* [必要]
* @param {*} props
* @param {*} state
* @returns
* @param {*} props
* @param {*} state
* @returns
*/
shouldComponentUpdate(props, state) {
return !isEqual(this.state, state)
@@ -78,29 +114,20 @@ export default class index extends Component {
* 加载字典数据,之后开始加载表格数据
* 如果必须要加载字典数据,可直接对表格设置autoLoad=true
*/
componentDidMount() {
this.table.current.onLoading()
getDictData('op_type').then(res => {
this.setState({
codes: res
}, () => {
this.table.current.onLoadData()
})
})
}
componentDidMount() {}
/**
* 调用加载数据接口,可在调用前对query进行处理
* [异步,必要]
* @param {*} params
* @param {*} query
* @returns
* @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;
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,
@@ -109,16 +136,16 @@ export default class index extends Component {
return data
}
/**
* 绑定字典数据
* @param {*} code
* @param {*} name
* @returns
*/
* 绑定字典数据
* @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)
const c = codes.find(p => +p.code === code)
if (c) {
return c.value
}
@@ -129,8 +156,8 @@ export default class index extends Component {
/**
* 对表格上的操作进行统一处理
* [异步]
* @param {*} action
* @param {*} successMessage
* @param {*} action
* @param {*} successMessage
*/
async onAction(action, successMessage) {
this.table.current.onLoading()
@@ -144,27 +171,27 @@ export default class index extends Component {
}
onOpLogClear() {
this.onAction(
apiAction.delete(),
'清空成功'
)
this.onAction(apiAction.delete(), '清空成功')
}
render() {
return (
<Container mode="fluid">
<br />
<Alert closable type="error" message={
<>
<div>后端bug:任何操作的操作类型都是增加</div>
<div>没有记录请求参数.返回结果等信息</div>
</>
} />
<Alert
closable
type="error"
message={
<>
<div>后端bug:任何操作的操作类型都是增加</div>
<div>没有记录请求参数.返回结果等信息</div>
</>
}
/>
<br />
<Card bordered={false}>
<QueryTable
ref={this.table}
autoLoad={false}
loadData={this.loadData}
columns={this.columns}
query={
@@ -172,41 +199,23 @@ export default class index extends Component {
<Form.Item label="日志名称" name="name">
<Input autoComplete="off" placeholder="请输入日志名称" />
</Form.Item>
<Form.Item label="操作类型" name="opType">
<Select placeholder="请选择操作类型">
{
this.state.codes.opType.map(item => {
return <Select.Option
key={item.code}
value={+item.code}
>{item.value}</Select.Option>
})
}
</Select>
</Form.Item>
<Form.Item label="是否成功" name="success">
<Select placeholder="请选择是否成功" style={{ width: '170px' }}>
<Select.Option
key='true'
>
</Select.Option>
<Select.Option
key='false'
>
</Select.Option>
<Select.Option key="true"></Select.Option>
<Select.Option key="false"></Select.Option>
</Select>
</Form.Item>
<Form.Item label="操作时间" name="dates">
<RangePicker
showTime={
{
hideDisabledOptions: true,
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')]
}
}
showTime={{
hideDisabledOptions: true,
defaultValue: [
moment('00:00:00', 'HH:mm:ss'),
moment('23:59:59', 'HH:mm:ss'),
],
}}
format="YYYY-MM-DD HH:mm:ss"
>
</RangePicker>
></RangePicker>
</Form.Item>
</Auth>
}
@@ -222,8 +231,8 @@ export default class index extends Component {
</Auth>
}
expandable={{
expandedRowRender: record =>
<Descriptions bordered size="small">
expandedRowRender: record => (
<Descriptions bordered size="small" labelStyle={{ width: '150px' }}>
<Descriptions.Item span="1" label="方法名称">
{record.methodName}
</Descriptions.Item>
@@ -240,20 +249,26 @@ export default class index extends Component {
{record.className}
</Descriptions.Item>
<Descriptions.Item span="3" label="返回结果">
{record.result}
<ReactJson
src={JSON.parse(record.result || '{}')}
collapsed
/>
</Descriptions.Item>
<Descriptions.Item span="3" label="请求参数">
{record.param}
<ReactJson
src={JSON.parse(record.param || '{}')}
collapsed
/>
</Descriptions.Item>
<Descriptions.Item span="3" label="具体消息">
{record.message}
</Descriptions.Item>
</Descriptions>
),
}}
/>
</Card>
</Container>
)
}
}
}

View File

@@ -1,5 +1,16 @@
import React, { Component } from 'react'
import { Alert, Button, Card, Descriptions, Form, Popconfirm, Input, message as Message, Select, DatePicker } from 'antd'
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'
@@ -7,17 +18,17 @@ import { isEqual } from 'lodash'
import getDictData from 'util/dic'
import moment from 'moment'
const { RangePicker } = DatePicker;
const { RangePicker } = DatePicker
const apiAction = {
page: api.sysVisLogPage,
delete: api.sysVisLogDelete
delete: api.sysVisLogDelete,
}
export default class index extends Component {
state = {
codes: {
visType: []
}
visType: [],
},
}
// 表格实例
table = React.createRef()
@@ -26,38 +37,54 @@ export default class index extends Component {
{
title: '日志名称',
dataIndex: 'name',
width: 200,
sorter: true,
},
{
title: '访问类型',
dataIndex: 'visType',
render: text => (<>{this.bindCodeValue(text, 'vis_type')}</>),
width: 120,
render: text => <>{this.bindCodeValue(text, 'vis_type')}</>,
sorter: true,
},
{
title: '是否成功',
dataIndex: 'success',
render: text => (<> {text ? '是' : '否'}</>),
width: 120,
render: text => (
<>
{text ? (
<span className="text-success"></span>
) : (
<span className="text-error"></span>
)}
</>
),
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,
},
]
@@ -66,9 +93,9 @@ export default class index extends Component {
* 阻止外部组件引发的渲染,提升性能
* 可自行添加渲染条件
* [必要]
* @param {*} props
* @param {*} state
* @returns
* @param {*} props
* @param {*} state
* @returns
*/
shouldComponentUpdate(props, state) {
return !isEqual(this.state, state)
@@ -81,26 +108,29 @@ export default class index extends Component {
componentDidMount() {
this.table.current.onLoading()
getDictData('vis_type').then(res => {
this.setState({
codes: res
}, () => {
this.table.current.onLoadData()
})
this.setState(
{
codes: res,
},
() => {
this.table.current.onLoadData()
}
)
})
}
/**
* 调用加载数据接口,可在调用前对query进行处理
* [异步,必要]
* @param {*} params
* @param {*} query
* @returns
* @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;
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,
@@ -109,16 +139,16 @@ export default class index extends Component {
return data
}
/**
* 绑定字典数据
* @param {*} code
* @param {*} name
* @returns
*/
* 绑定字典数据
* @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)
const c = codes.find(p => +p.code === code)
if (c) {
return c.value
}
@@ -129,8 +159,8 @@ export default class index extends Component {
/**
* 对表格上的操作进行统一处理
* [异步]
* @param {*} action
* @param {*} successMessage
* @param {*} action
* @param {*} successMessage
*/
async onAction(action, successMessage) {
this.table.current.onLoading()
@@ -144,17 +174,18 @@ export default class index extends Component {
}
onVisLogClear() {
this.onAction(
apiAction.delete(),
'清空成功'
)
this.onAction(apiAction.delete(), '清空成功')
}
render() {
return (
<Container mode="fluid">
<br />
<Alert closable type="error" message="页面刷新的时候也会有保存登录日志但是没有ip显示" />
<Alert
closable
type="error"
message="页面刷新的时候也会有保存登录日志但是没有ip显示"
/>
<br />
<Card bordered={false}>
<QueryTable
@@ -168,43 +199,37 @@ export default class index extends Component {
<Input autoComplete="off" placeholder="请输入日志名称" />
</Form.Item>
<Form.Item label="访问类型" name="visType">
<Select placeholder="请选择访问类型"
<Select
placeholder="请选择访问类型"
allow-clear
style={{ width: '170px' }}
>
{
this.state.codes.visType.map(item => {
return <Select.Option
key={item.code}
value={+item.code}
>{item.value}</Select.Option>
})
}
{this.state.codes.visType.map(item => {
return (
<Select.Option key={item.code} value={+item.code}>
{item.value}
</Select.Option>
)
})}
</Select>
</Form.Item>
<Form.Item label="是否成功" name="success">
<Select placeholder="请选择是否成功" style={{ width: '170px' }}>
<Select.Option
key='true'
>
</Select.Option>
<Select.Option
key='false'
>
</Select.Option>
<Select.Option key="true"></Select.Option>
<Select.Option key="false"></Select.Option>
</Select>
</Form.Item>
<Form.Item label="访问时间" name="dates">
<RangePicker
showTime={
{
hideDisabledOptions: true,
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')]
}
}
showTime={{
hideDisabledOptions: true,
defaultValue: [
moment('00:00:00', 'HH:mm:ss'),
moment('23:59:59', 'HH:mm:ss'),
],
}}
format="YYYY-MM-DD HH:mm:ss"
>
</RangePicker>
></RangePicker>
</Form.Item>
</Auth>
}
@@ -220,16 +245,22 @@ export default class index extends Component {
</Auth>
}
expandable={{
expandedRowRender: record =>
<Descriptions bordered size="small">
<Descriptions.Item span="3" label="具体消息">
expandedRowRender: record => (
<Descriptions
bordered
size="small"
columns={1}
labelStyle={{ width: '150px' }}
>
<Descriptions.Item label="具体消息">
{record.message}
</Descriptions.Item>
</Descriptions>
),
}}
/>
</Card>
</Container>
)
}
}
}

View File

@@ -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 (
<>
<Col span={24}>
<Card bordered={false}>
<Descriptions
column={2}
labelStyle={{ fontSize: '14px', color: 'rgba(0,0,0,.45)' }}
contentStyle={{ fontSize: '14px' }}
>
<Descriptions.Item label="主机名称">{hostName}</Descriptions.Item>
<Descriptions.Item label="操作系统">{systemOs}</Descriptions.Item>
<Descriptions.Item label="外网信息">{wanIp}</Descriptions.Item>
<Descriptions.Item label="内网IP">{lanIp}</Descriptions.Item>
<Descriptions.Item label="系统架构">{osArchitecture}</Descriptions.Item>
<Descriptions.Item label="运行框架">
{frameworkDescription}
</Descriptions.Item>
</Descriptions>
</Card>
</Col>
</>
)
}
}

View File

@@ -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) => (
<Col key={i} xl={6} lg={12}>
<Card bordered={false}>
<div className="h4 mb-md">
{item.description}({item.name})
</div>
<div className="h-200" ref={`disk-chart-${i}`}></div>
</Card>
</Col>
))}
</>
)
}
}

View File

@@ -0,0 +1,44 @@
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() {
const { data: base } = await api.sysMachineBase()
this.setState({ loading: false, base })
}
render() {
const { paneActived } = this.props
const { loading } = this.state
return (
<Container mode="fluid">
<br />
<Row gutter={16}>
{!loading && <Base {...this.state} />}
{!loading && <UseCharts {...this.state} actived={paneActived} />}
{!loading && <DiskCharts {...this.state} />}
</Row>
</Container>
)
}
}

View File

@@ -0,0 +1,297 @@
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
systemStart = moment()
now = Date.now()
cpuChart = null
cpuData = []
ramChart = null
ramData = []
shouldComponentUpdate(props) {
// 当前页签未选中时停止获取状态
if (this.props.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.timer = setInterval(() => {
this.refreshData()
}, 3000)
this.refreshData()
this.timerMoment = setInterval(() => {
this.setState({ nowMoment: moment() })
}, 1000)
}
stop() {
clearInterval(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 })
}
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 (
<>
<Col xl={12} lg={24}>
<Card bordered={false}>
<Row align="bottom" justify="space-between">
<div className="h2">CPU</div>
<div className="h5">{cpuName}</div>
</Row>
<div className="h-200 mt-md mb-md" ref="cpu-chart"></div>
<Row>
<Col flex="1">
<Statistic title="使用率" value={(cpuRate || 0) + ' %'} />
<Statistic title="正常运行时间" value={diff} />
</Col>
<Col flex="1">
<Descriptions column={1}>
<Descriptions.Item label="基准速度">
<b>{((cpuBaseSpeed || 0) / 1000).toFixed(2)} GHz</b>
</Descriptions.Item>
<Descriptions.Item label="逻辑处理器">
<b>{processorCount || 0}</b>
</Descriptions.Item>
</Descriptions>
</Col>
</Row>
</Card>
</Col>
<Col xl={12} lg={24}>
<Card bordered={false}>
<Row align="bottom" justify="space-between">
<div className="h2">内存</div>
<div className="h5">{((totalRam || 0) / 1024).toFixed(1)} GB</div>
</Row>
<div className="h-200 mt-md mb-md" ref="ram-chart"></div>
<Row>
<Col flex="1">
<Statistic
title="使用中"
value={
(((totalRam || 0) / 1024) * ((ramRate || 1) / 100)).toFixed(
1
) + ' GB'
}
/>
<Statistic
title="可用"
value={
(
((totalRam || 0) / 1024) *
(1 - (ramRate || 1) / 100)
).toFixed(1) + ' GB'
}
/>
</Col>
</Row>
</Card>
</Col>
</>
)
}
}

View File

@@ -7,29 +7,31 @@ import { api } from 'common/api'
import { EMPTY_ID } from 'util/global'
const initialValues = {
type: '1',
openType: '1',
type: 1,
openType: 1,
weight: '2',
visible: true,
sort: 100
sort: 100,
}
export default class form extends Component {
state = {
// 加载状态
loading: true,
codes: {
menuType: [],
openType: []
openType: [],
menuWeight: [],
},
options: {
appList: [],
parentTreeData: []
parentTreeData: [],
},
addType: [],
type: initialValues.type,
openType: initialValues.openType,
icon: ''
icon: '',
}
// 表单实例
@@ -51,45 +53,49 @@ export default class form extends Component {
* 填充数据
* 可以在设置this.record之后对其作出数据结构调整
* [异步,必要]
* @param {*} params
* @param {*} params
*/
async fillData(params) {
const form = this.form.current
this.record = cloneDeep(params.record)
//#region 从后端转换成前段所需格式
const { menuType, openType } = await getDictData('menu_type', 'open_type')
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)
} else if (params.record) {
}
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: {
menuType,
openType
},
codes,
options: {
appList,
parentTreeData
parentTreeData,
},
icon
icon,
})
//#endregion
const form = this.form.current
if (params.isParent) {
form.setFieldsValue({
pid: params.parent.id,
application: params.parent.application
application: params.parent.application,
})
} else {
form.setFieldsValue(this.record)
}
this.setState({
loading: false
})
this.setState({ loading: false })
this.onTypeChange()
}
@@ -98,7 +104,7 @@ export default class form extends Component {
* 获取数据
* 可以对postData进行数据结构调整
* [异步,必要]
* @returns
* @returns
*/
async getData() {
const form = this.form.current
@@ -123,30 +129,31 @@ export default class form extends Component {
async onLoadMenuTree(application) {
const { data } = await api.getMenuTree({ application })
return [{
id: EMPTY_ID,
parentId: undefined,
title: '顶级',
value: EMPTY_ID,
pid: undefined,
children: data,
}]
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'
})
}
// const form = this.form.current
// const { type } = form.getFieldsValue()
// if ([0, 2].includes(type)) {
// form.setFieldsValue({
// openType: 0,
// })
// } else {
// form.setFieldsValue({
// openType: 1,
// })
// }
}
onOpenTypeChange() {
@@ -168,178 +175,221 @@ export default class form extends Component {
this.setState({
type,
openType
openType,
})
}
async onApplicationChange(value) {
this.setState({
loading: true
loading: true,
})
const parentTreeData = await this.onLoadMenuTree(value)
this.setState({
loading: false,
options: {
...this.state.options,
parentTreeData
}
parentTreeData,
},
})
this.form.current.setFieldsValue({
pid: undefined
pid: undefined,
})
}
onSelectIcon(icon) {
this.form.current.setFieldsValue({
icon
icon,
})
this.setState({ icon })
}
//#endregion
render() {
const { loading, codes, options, addType, type, openType, icon } = this.state
return (
<Form
initialValues={initialValues}
ref={this.form}
className="yo-form"
>
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
<Form initialValues={initialValues} ref={this.form} className="yo-form">
<Spin spinning={loading} indicator={<AntIcon type="loading" />}>
<div className="yo-form-group">
<h3 className="h3">基本信息</h3>
<div className="yo-form-group">
<Form.Item
label="菜单类型"
name="type"
help={
tooltip={
<>
目录默认添加在顶级
<br />菜单
<br />按钮
目录一级菜单默认添加在顶级
<br />
菜单二级菜单
<br />
按钮菜单中对应到接口的功能
</>
}
rules={[{ required: true, message: '请选择菜单类型' }]}
>
<Radio.Group onChange={(e) => this.onTypeChange(e)}>
{
this.state.codes.menuType.map(item => {
return (
<Radio.Button
key={item.code}
value={item.code}
>{item.value}</Radio.Button>
)
})
}
<Radio.Group onChange={e => this.onTypeChange(e)}>
{codes.menuType.map(item => {
return (
<Radio.Button
key={item.code}
value={+item.code}
disabled={!addType.includes(+item.code)}
>
{item.value}
</Radio.Button>
)
})}
</Radio.Group>
</Form.Item>
<Form.Item label="名称" name="name" rules={[{ required: true, message: '请输入名称' }]}>
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '请输入名称' }]}
>
<Input autoComplete="off" placeholder="请输入名称" />
</Form.Item>
<Form.Item label="唯一编码" name="code">
<Input autoComplete="off" placeholder="请输入唯一编码" />
</Form.Item>
<Form.Item label="所属应用" name="application" rules={[{ required: true, message: '请选择所属应用' }]}>
<Select placeholder="请选择所属应用" onChange={(value) => this.onApplicationChange(value)}>
{
this.state.options.appList.map(item => {
return (
<Select.Option
key={item.code}
value={item.code}
>{item.name}</Select.Option>
)
})
}
<Form.Item
label="所属应用"
name="application"
rules={[{ required: true, message: '请选择所属应用' }]}
>
<Select
placeholder="请选择所属应用"
onChange={value => this.onApplicationChange(value)}
>
{options.appList.map(item => {
return (
<Select.Option key={item.code} value={item.code}>
{item.name}
</Select.Option>
)
})}
</Select>
</Form.Item>
{
this.state.type != 0 &&
<Form.Item label="父级菜单" name="pid" rules={[{ required: true, message: '请选择父级' }]}>
{type != 0 && (
<Form.Item
label="父级菜单"
name="pid"
rules={[{ required: true, message: '请选择父级' }]}
>
<TreeSelect
dropdownStyle={{ maxHeight: '300px', overflow: 'auto' }}
treeData={this.state.options.parentTreeData}
treeData={options.parentTreeData}
placeholder="请选择父级菜单"
/>
</Form.Item>
}
)}
<Form.Item
label="权重"
name="weight"
tooltip={
<>
系统权重菜单/功能任何角色可用
<br />
业务权重菜单/功能为超级管理员不可用可防止管理员误操作
</>
}
rules={[{ required: true, message: '请选择权重' }]}
>
<Radio.Group>
{codes.menuWeight.map(item => {
return (
<Radio.Button key={item.code} value={item.code}>
{item.value}
</Radio.Button>
)
})}
</Radio.Group>
</Form.Item>
</div>
<h3 className="h3">扩展信息</h3>
<div className="yo-form-group">
{
this.state.type == 1 &&
{type == 1 && (
<Form.Item label="打开方式" name="openType">
<Radio.Group onChange={(e) => this.onOpenTypeChange(e)}>
{
this.state.codes.openType.map(item => {
return (
<Radio.Button
key={item.code}
value={item.code}
>{item.value}</Radio.Button>
)
})
}
<Radio.Group onChange={e => this.onOpenTypeChange(e)}>
{codes.openType.map(item => {
return (
<Radio.Button key={item.code} value={+item.code}>
{item.value}
</Radio.Button>
)
})}
</Radio.Group>
</Form.Item>
}
{
this.state.type == 1 && this.state.openType == 1 &&
<Form.Item label="前端组件" name="component" rules={[{ required: true, message: '请输入前端组件' }]}>
)}
{type == 1 && openType == 1 && (
<Form.Item
label="前端组件"
name="component"
tooltip="打开新页签并渲染前端组件"
rules={[{ required: true, message: '请输入前端组件' }]}
>
<Input autoComplete="off" placeholder="请输入前端组件" />
</Form.Item>
}
{
this.state.type == 1 && this.state.openType == 2 &&
<Form.Item label="内链地址" name="router" rules={[{ required: true, message: '请输入内链地址' }]}>
)}
{type == 1 && openType == 2 && (
<Form.Item
label="内链地址"
name="link"
tooltip="打开新页签并使用iframe加载页面"
rules={[{ required: true, message: '请输入内链地址' }]}
>
<Input autoComplete="off" placeholder="请输入内链地址" />
</Form.Item>
}
{
this.state.type == 1 && this.state.openType == 3 &&
<Form.Item label="外链地址" name="link" rules={[{ required: true, message: '请输入外链地址' }]}>
)}
{type == 1 && openType == 3 && (
<Form.Item
label="外链地址"
name="redirect"
tooltip="打开新的浏览器窗口"
rules={[{ required: true, message: '请输入外链地址' }]}
>
<Input autoComplete="off" placeholder="请输入外链地址" />
</Form.Item>
}
{
this.state.type == 2 &&
<Form.Item label="权限标识" name="permission" rules={[{ required: true, message: '请输入权限标识' }]}>
)}
{type == 2 && (
<Form.Item
label="权限标识"
name="permission"
rules={[{ required: true, message: '请输入权限标识' }]}
>
<Input autoComplete="off" placeholder="请输入权限标识" />
</Form.Item>
}
{
this.state.type == 2 &&
<Form.Item label="关联上级菜单显示" name="visibleParent" valuePropName="checked">
)}
{type == 2 && (
<Form.Item
label="关联上级菜单显示"
name="visibleParent"
valuePropName="checked"
>
<Switch />
</Form.Item>
}
)}
<Form.Item label="可见性" name="visible" valuePropName="checked">
<Switch />
</Form.Item>
{
this.state.type != 2 &&
{type != 2 && (
<Form.Item label="图标" name="icon">
<Input
disabled
placeholder="请选择图标"
addonBefore={
this.state.icon &&
<AntIcon type={this.state.icon} />
}
addonBefore={icon && <AntIcon type={icon} />}
addonAfter={
<AntIcon
type="setting"
onClick={() =>
this
.iconSelector
.current
.open(this.form.current.getFieldValue('icon'))
this.iconSelector.current.open(
this.form.current.getFieldValue('icon')
)
}
/>
}
/>
</Form.Item>
}
)}
<Form.Item label="排序" name="sort">
<InputNumber
max={1000}
@@ -355,7 +405,7 @@ export default class form extends Component {
</div>
</Spin>
<IconSelector ref={this.iconSelector} onSelect={(icon) => this.onSelectIcon(icon)} />
<IconSelector ref={this.iconSelector} onSelect={icon => this.onSelectIcon(icon)} />
</Form>
)
}

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react'
import { Button, Table, Card, Form, Input, Popconfirm, message as Message, Tag } from 'antd'
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'
@@ -24,6 +24,7 @@ export default class index extends Component {
codes: {
menuType: [],
menuWeight: [],
openType: [],
},
}
@@ -55,16 +56,36 @@ export default class index extends Component {
render: text => text && <AntIcon type={text} />,
},
{
title: '前端组件',
title: '连接',
width: 220,
dataIndex: 'component',
ellipsis: true,
},
{
title: '权限标识',
width: 220,
dataIndex: 'permission',
ellipsis: true,
dataIndex: 'openType',
render: (text, record) => {
switch (text) {
case 1:
return (
<>
<Tag color="green">{this.bindCodeValue(text, 'open_type')}</Tag>{' '}
{record.component}
</>
)
case 2:
return (
<>
<Tag color="orange">{this.bindCodeValue(text, 'open_type')}</Tag>{' '}
{record.link}
</>
)
case 3:
return (
<>
<Tag color="red">{this.bindCodeValue(text, 'open_type')}</Tag>{' '}
{record.redirect}
</>
)
default:
return ''
}
},
},
{
title: '排序',
@@ -85,19 +106,14 @@ export default class index extends Component {
if (flag) {
this.columns.push({
title: '操作',
width: 150,
width: 220,
dataIndex: 'actions',
render: (text, record) => (
<QueryTableActions>
{record.type < 2 && (
<Auth auth="sysMenu:add">
<a onClick={() => this.onOpen(this.addForm, record, true)}>
新增子菜单
</a>
</Auth>
)}
<Auth auth="sysMenu:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
<a onClick={() => this.onOpen({ modal: this.editForm, record })}>
编辑
</a>
</Auth>
<Auth auth="sysMenu:delete">
<Popconfirm
@@ -108,6 +124,22 @@ export default class index extends Component {
<a>删除</a>
</Popconfirm>
</Auth>
{record.type < 2 && (
<Auth auth="sysMenu:add">
<a
onClick={() =>
this.onOpen({
modal: this.addForm,
record,
isParent: true,
addType: record.type == 0 ? [1] : [2],
})
}
>
{record.type == 0 ? '新增子菜单' : '新增功能'}
</a>
</Auth>
)}
</QueryTableActions>
),
})
@@ -132,7 +164,7 @@ export default class index extends Component {
*/
componentDidMount() {
this.table.current.onLoading()
getDictData('menu_type', 'menu_weight').then(res => {
getDictData('menu_type', 'menu_weight', 'open_type').then(res => {
this.setState(
{
codes: res,
@@ -156,27 +188,8 @@ export default class index extends Component {
...params,
...query,
})
return this.onfilterdata(data)
//return data
}
onfilterdata(data) {
this.findlastChildren(data)
return data
}
findlastChildren(data) {
data.forEach(s => {
if (s.children && s.children.length > 0) {
s.rowExpandable = true
s.children.forEach(p => {
if (p.children && p.children.length === 0) {
p.rowExpandable = false
}
})
this.findlastChildren(s.children)
}
})
}
/**
* 绑定字典数据
@@ -188,7 +201,7 @@ export default class index extends Component {
name = toCamelCase(name)
const codes = this.state.codes[name]
if (codes) {
const c = codes.find(p => p.code === code)
const c = codes.find(p => p.code == code)
if (c) {
return c.value
}
@@ -201,15 +214,17 @@ export default class index extends Component {
* @param {*} modal
* @param {*} record
*/
onOpen(modal, record, isParent = false) {
onOpen({ modal, record, isParent = false, addType = [] }) {
const params = isParent
? {
parent: record,
isParent,
addType,
}
: {
record,
isParent,
addType,
}
modal.current.open(params)
@@ -240,64 +255,106 @@ export default class index extends Component {
this.onAction(apiAction.delete(record), '删除成功')
}
//#region 自定义方法
/**
* 绘制新的扩展行
* @param {*} record
*/
onRowRender(record) {
var arr = []
var istag = false
record.children.map(s => {
if (!s.rowExpandable && s.type == 2) {
istag = true
var temp = (
<Tag color="#87d068" key={s.id}>
{s.name} |{' '}
<Auth auth="sysMenu:edit">
<a onClick={() => this.onOpen(this.editForm, s)}>编辑</a>
</Auth>{' '}
|{' '}
<Auth auth="sysMenu:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(s)}
>
<a>删除</a>
</Popconfirm>
</Auth>
</Tag>
const grids = []
let isFunction = false
record.children.map(item => {
if (!(item.children && item.children.length) && item.type == 2) {
isFunction = true
const grid = (
<Card.Grid key={item.id}>
<p>
{item.visibleParent && (
<Tooltip placement="top" title="选中此项才会显示菜单">
<AntIcon
type="eye"
style={{ color: '#1890ff' }}
className="mr-xxs"
/>
</Tooltip>
)}
<b>{item.name}</b>
</p>
<span className="block ellipsis text-gray">{item.permission}</span>
<div className="text-right">
<QueryTableActions>
<Auth auth="sysMenu:edit">
<Tooltip title="编辑">
<a
className="link-gray"
onClick={() =>
this.onOpen({ modal: this.editForm, record: item })
}
>
<AntIcon type="edit" />
</a>
</Tooltip>
</Auth>
<Auth auth="sysMenu:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(item)}
>
<Tooltip title="删除">
<a className="link-gray">
<AntIcon type="delete" />
</a>
</Tooltip>
</Popconfirm>
</Auth>
</QueryTableActions>
</div>
</Card.Grid>
)
arr.push(temp)
} else if (s.rowExpandable || (!s.rowExpandable && s.type == 1)) {
arr.push(s)
grids.push(grid)
} else if (
(item.children && item.children.length) ||
(!(item.children && item.children.length) && item.type == 1)
) {
grids.push(item)
}
})
if (istag) {
return arr
if (isFunction) {
grids.push(
<Card.Grid
key={0}
style={{ padding: '18px 12px', cursor: 'pointer' }}
className="text-center"
onClick={() =>
this.onOpen({ modal: this.addForm, record, isParent: true, addType: [2] })
}
>
<div>
<AntIcon type="plus" className="text-normal h2" />
</div>
<span className="text-gray">新增功能</span>
</Card.Grid>
)
return <Card bordered={false}>{grids}</Card>
} else {
return (
<Table
columns={this.columns}
showHeader={false}
dataSource={arr}
dataSource={grids}
rowKey={record => record.id}
expandable={{
expandedRowRender: record => this.onRowRender(record),
rowExpandable: record => record.rowExpandable === true,
rowExpandable: record => record.children && record.children.length,
}}
childrenColumnName="11"
childrenColumnName="none"
bordered={true}
pagination={false}
/>
)
}
}
//#region 自定义方法
async onSetDefault(record) {
this.onAction(apiAction.setDefault(record), '设置成功')
}
//#endregion
render() {
@@ -306,23 +363,25 @@ export default class index extends Component {
<br />
<Card bordered={false}>
<QueryTable
className="yo-inner-table"
childrenColumnName="111"
className="yo-menu-table"
childrenColumnName="none"
ref={this.table}
autoLoad={false}
loadData={this.loadData}
columns={this.columns}
expandable={{
expandedRowRender: record => this.onRowRender(record),
rowExpandable: record => record.rowExpandable === true,
rowExpandable: record => record.children && record.children.length,
}}
operator={
<Auth auth="sysMenu:add">
<Button
icon={<AntIcon type="plus" />}
onClick={() => this.onOpen(this.addForm)}
onClick={() =>
this.onOpen({ modal: this.addForm, addType: [0, 1] })
}
>
新增{name}
新增目录/菜单
</Button>
</Auth>
}

View File

@@ -1,6 +1,14 @@
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 {
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'
@@ -13,17 +21,16 @@ const apiAction = {
page: api.getOrgPage,
add: api.sysOrgAdd,
edit: api.sysOrgEdit,
delete: api.sysOrgDelete
delete: api.sysOrgDelete,
}
const name = '机构'
export default class index extends Component {
state = {
codes: {
orgType: []
}
orgType: [],
},
}
// 树框架实例
@@ -44,13 +51,13 @@ export default class index extends Component {
columns = [
{
title: '机构名称',
width: '400px',
width: 400,
dataIndex: 'name',
sorter: true,
},
{
title: '唯一编码',
width: '200px',
width: 200,
dataIndex: 'code',
sorter: true,
},
@@ -58,24 +65,26 @@ export default class index extends Component {
title: '机构类型',
dataIndex: 'type',
sorter: true,
render: text => (<>{this.bindCodeValue(text, 'org_type')}</>)
render: text => <>{this.bindCodeValue(text, 'org_type')}</>,
},
{
title: '排序',
width: '80px',
width: 80,
dataIndex: 'sort',
sorter: true,
defaultSortOrder: 'ascend',
},
{
title: '备注',
dataIndex: 'remark',
width: 400,
sorter: true,
},
]
/**
* 构造函数,在渲染前动态添加操作字段等
* @param {*} props
* @param {*} props
*/
constructor(props) {
super(props)
@@ -87,20 +96,22 @@ export default class index extends Component {
title: '操作',
width: 150,
dataIndex: 'actions',
render: (text, record) => (<QueryTableActions>
<Auth auth="sysOrg:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
</Auth>
<Auth auth="sysOrg:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
>
<a>删除</a>
</Popconfirm>
</Auth>
</QueryTableActions>)
render: (text, record) => (
<QueryTableActions>
<Auth auth="sysOrg:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
</Auth>
<Auth auth="sysOrg:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
>
<a>删除</a>
</Popconfirm>
</Auth>
</QueryTableActions>
),
})
}
}
@@ -109,9 +120,9 @@ export default class index extends Component {
* 阻止外部组件引发的渲染,提升性能
* 可自行添加渲染条件
* [必要]
* @param {*} props
* @param {*} state
* @returns
* @param {*} props
* @param {*} state
* @returns
*/
shouldComponentUpdate(props, state) {
return !isEqual(this.state, state)
@@ -124,26 +135,28 @@ export default class index extends Component {
componentDidMount() {
this.table.current.onLoading()
getDictData('org_type').then(res => {
this.setState({
codes: res
}, () => {
this.table.current.onLoadData()
})
this.setState(
{
codes: res,
},
() => {
this.table.current.onLoadData()
}
)
})
}
/**
* 调用加载数据接口,可在调用前对query进行处理
* [异步,必要]
* @param {*} params
* @param {*} query
* @returns
* @param {*} params
* @param {*} query
* @returns
*/
loadData = async (params, query) => {
query = {
...query,
pid: this.selectId
pid: this.selectId,
}
const { data } = await apiAction.page({
@@ -166,7 +179,7 @@ export default class index extends Component {
/**
* 树节点选中事件
* [必要]
* @param {*} id
* @param {*} id
*/
onSelectTree(id) {
this.selectId = id
@@ -175,15 +188,15 @@ export default class index extends Component {
/**
* 绑定字典数据
* @param {*} code
* @param {*} name
* @returns
* @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)
const c = codes.find(p => p.code == code)
if (c) {
return c.value
}
@@ -193,21 +206,21 @@ export default class index extends Component {
/**
* 打开新增/编辑弹窗
* @param {*} modal
* @param {*} record
* @param {*} modal
* @param {*} record
*/
onOpen(modal, record) {
modal.current.open({
orgId: this.selectId,
record
record,
})
}
/**
* 对表格上的操作进行统一处理
* [异步]
* @param {*} action
* @param {*} successMessage
* @param {*} action
* @param {*} successMessage
*/
async onAction(action, successMessage) {
this.table.current.onLoading()
@@ -227,13 +240,10 @@ export default class index extends Component {
/**
* 删除
* @param {*} record
* @param {*} record
*/
onDelete(record) {
this.onAction(
apiAction.delete(record),
'删除成功'
)
this.onAction(apiAction.delete(record), '删除成功')
}
//#region 自定义方法
@@ -245,7 +255,7 @@ export default class index extends Component {
ref={this.treeLayout}
loadData={this.loadTreeData}
defaultExpanded={true}
onSelect={(key) => this.onSelectTree(key)}
onSelect={key => this.onSelectTree(key)}
>
<Container mode="fluid">
<Card bordered={false}>
@@ -265,7 +275,9 @@ export default class index extends Component {
<Button
icon={<AntIcon type="plus" />}
onClick={() => this.onOpen(this.addForm)}
>新增{name}</Button>
>
新增{name}
</Button>
}
/>
</Card>

View File

@@ -2,24 +2,22 @@ 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 { 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
delete: api.sysPosDelete,
}
// 用于弹窗标题
const name = '职位'
export default class index extends Component {
// 表格实例
table = React.createRef()
@@ -32,29 +30,34 @@ export default class index extends Component {
{
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
*/
* 构造函数,在渲染前动态添加操作字段等
* @param {*} props
*/
constructor(props) {
super(props)
@@ -65,20 +68,22 @@ export default class index extends Component {
title: '操作',
width: 150,
dataIndex: 'actions',
render: (text, record) => (<QueryTableActions>
<Auth auth="sysApp:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
</Auth>
<Auth auth="sysApp:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
>
<a>删除</a>
</Popconfirm>
</Auth>
</QueryTableActions>)
render: (text, record) => (
<QueryTableActions>
<Auth auth="sysApp:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
</Auth>
<Auth auth="sysApp:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
>
<a>删除</a>
</Popconfirm>
</Auth>
</QueryTableActions>
),
})
}
}
@@ -87,9 +92,9 @@ export default class index extends Component {
* 阻止外部组件引发的渲染,提升性能
* 可自行添加渲染条件
* [必要]
* @param {*} props
* @param {*} state
* @returns
* @param {*} props
* @param {*} state
* @returns
*/
shouldComponentUpdate(props, state) {
return !isEqual(this.state, state)
@@ -98,9 +103,9 @@ export default class index extends Component {
/**
* 调用加载数据接口,可在调用前对query进行处理
* [异步,必要]
* @param {*} params
* @param {*} query
* @returns
* @param {*} params
* @param {*} query
* @returns
*/
loadData = async (params, query) => {
const { data } = await apiAction.page({
@@ -111,21 +116,21 @@ export default class index extends Component {
}
/**
* 打开新增/编辑弹窗
* @param {*} modal
* @param {*} record
*/
* 打开新增/编辑弹窗
* @param {*} modal
* @param {*} record
*/
onOpen(modal, record) {
modal.current.open({
record
record,
})
}
/**
* 对表格上的操作进行统一处理
* [异步]
* @param {*} action
* @param {*} successMessage
* @param {*} action
* @param {*} successMessage
*/
async onAction(action, successMessage) {
this.table.current.onLoading()
@@ -140,13 +145,10 @@ export default class index extends Component {
/**
* 删除
* @param {*} record
* @param {*} record
*/
onDelete(record) {
this.onAction(
apiAction.delete(record),
'删除成功'
)
this.onAction(apiAction.delete(record), '删除成功')
}
render() {
@@ -172,11 +174,11 @@ export default class index extends Component {
<Button
icon={<AntIcon type="plus" />}
onClick={() => this.onOpen(this.addForm)}
>新增{name}</Button>
>
新增{name}
</Button>
}
>
</QueryTable>
></QueryTable>
</Card>
<ModalForm
title={`新增${name}`}
@@ -198,4 +200,4 @@ export default class index extends Component {
</Container>
)
}
}
}

View File

@@ -10,7 +10,6 @@ const { SHOW_PARENT } = TreeSelect
const initialValues = {}
export default class data extends Component {
state = {
// 加载状态
loading: true,
@@ -19,7 +18,7 @@ export default class data extends Component {
arerTreeData: [],
orgCheckedKeys: [],
isDefine: false
isDefine: false,
}
// 表单实例
@@ -39,10 +38,9 @@ export default class data extends Component {
* 填充数据
* 可以在设置this.record之后对其作出数据结构调整
* [异步,必要]
* @param {*} params
* @param {*} params
*/
async fillData(params) {
this.record = cloneDeep(params.record)
//#region 从后端转换成前段所需格式
const { dataScopeType } = await getDictData('data_scope_type')
@@ -53,17 +51,17 @@ export default class data extends Component {
dataScopeType,
orgTreeData,
arerTreeData,
orgCheckedKeys
orgCheckedKeys,
})
//#endregion
this.form.current.setFieldsValue({
dataScopeType: this.record.dataScopeType.toString()
dataScopeType: this.record.dataScopeType.toString(),
})
this.onChange(this.record.dataScopeType)
this.setState({
loading: false
loading: false,
})
}
@@ -71,7 +69,7 @@ export default class data extends Component {
* 获取数据
* 可以对postData进行数据结构调整
* [异步,必要]
* @returns
* @returns
*/
async getData() {
const form = this.form.current
@@ -107,11 +105,11 @@ export default class data extends Component {
onChange(value) {
if (value == 5) {
this.setState({
isDefine: true
isDefine: true,
})
} else {
this.setState({
isDefine: false
isDefine: false,
})
}
}
@@ -119,29 +117,24 @@ export default class data extends Component {
render() {
return (
<Form
initialValues={initialValues}
ref={this.form}
className="yo-form"
>
<Form initialValues={initialValues} ref={this.form} className="yo-form">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
<div className="yo-form-group">
<Form.Item label="授权范围" name="dataScopeType">
<Select placeholder="请选择授权范围" onChange={(value) => this.onChange(value)}>
{
this.state.dataScopeType.map(item => {
return (
<Select.Option
key={item.code}
value={item.code}
>{item.value}</Select.Option>
)
})
}
<Select
placeholder="请选择授权范围"
onChange={value => this.onChange(value)}
>
{this.state.dataScopeType.map(item => {
return (
<Select.Option key={item.code} value={item.code}>
{item.value}
</Select.Option>
)
})}
</Select>
</Form.Item>
{
this.state.isDefine &&
{this.state.isDefine && (
<>
<Form.Item label="选择机构" name="grantOrgIdList">
<TreeSelect
@@ -151,7 +144,11 @@ export default class data extends Component {
treeCheckable
/>
</Form.Item>
<Form.Item label="选择区域" name="grantAreaCodeList" help="缺少获取已保存的列表,react版本节点名无法设置">
<Form.Item
label="选择区域"
name="grantAreaCodeList"
help="缺少获取已保存的列表,react版本节点名无法设置"
>
<TreeSelect
showCheckedStrategy={SHOW_PARENT}
treeData={this.state.arerTreeData}
@@ -160,7 +157,7 @@ export default class data extends Component {
/>
</Form.Item>
</>
}
)}
</div>
</Spin>
</Form>

View File

@@ -16,14 +16,13 @@ const apiAction = {
delete: api.sysRoleDelete,
grantMenu: api.sysRoleGrantMenu,
grantData: api.sysRoleGrantData
grantData: api.sysRoleGrantData,
}
// 用于弹窗标题
const name = '角色'
export default class index extends Component {
// 表格实例
table = React.createRef()
@@ -39,23 +38,27 @@ export default class index extends Component {
{
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
* @param {*} props
*/
constructor(props) {
super(props)
@@ -67,44 +70,58 @@ export default class index extends Component {
title: '操作',
width: 150,
dataIndex: 'actions',
render: (text, record) => (<QueryTableActions>
<Auth auth="sysRole:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
</Auth>
<Auth auth="sysRole:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
>
<a>删除</a>
</Popconfirm>
</Auth>
<Auth auth={{ sysRole: [['grantMenu'], ['grantData']] }}>
<Dropdown
placement="bottomRight"
overlay={
<Menu>
<Auth auth="sysRole:grantMenu">
<Menu.Item>
<a onClick={() => this.onOpen(this.menuForm, record)}>授权菜单</a>
</Menu.Item>
</Auth>
<Auth auth="sysRole:grantData">
<Menu.Item>
<a onClick={() => this.onOpen(this.dataForm, record)}>授权数据</a>
</Menu.Item>
</Auth>
</Menu>
}
>
<a className="ant-dropdown-link">
授权
<AntIcon type="down" />
</a>
</Dropdown>
</Auth>
</QueryTableActions>)
render: (text, record) => (
<QueryTableActions>
<Auth auth="sysRole:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
</Auth>
<Auth auth="sysRole:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
>
<a>删除</a>
</Popconfirm>
</Auth>
<Auth auth={{ sysRole: [['grantMenu'], ['grantData']] }}>
<Dropdown
placement="bottomRight"
overlay={
<Menu>
<Auth auth="sysRole:grantMenu">
<Menu.Item>
<a
onClick={() =>
this.onOpen(this.menuForm, record)
}
>
授权菜单
</a>
</Menu.Item>
</Auth>
<Auth auth="sysRole:grantData">
<Menu.Item>
<a
onClick={() =>
this.onOpen(this.dataForm, record)
}
>
授权数据
</a>
</Menu.Item>
</Auth>
</Menu>
}
>
<a className="ant-dropdown-link">
授权
<AntIcon type="down" />
</a>
</Dropdown>
</Auth>
</QueryTableActions>
),
})
}
}
@@ -113,9 +130,9 @@ export default class index extends Component {
* 阻止外部组件引发的渲染,提升性能
* 可自行添加渲染条件
* [必要]
* @param {*} props
* @param {*} state
* @returns
* @param {*} props
* @param {*} state
* @returns
*/
shouldComponentUpdate(props, state) {
return !isEqual(this.state, state)
@@ -124,9 +141,9 @@ export default class index extends Component {
/**
* 调用加载数据接口,可在调用前对query进行处理
* [异步,必要]
* @param {*} params
* @param {*} query
* @returns
* @param {*} params
* @param {*} query
* @returns
*/
loadData = async (params, query) => {
const { data } = await apiAction.page({
@@ -138,20 +155,20 @@ export default class index extends Component {
/**
* 打开新增/编辑弹窗
* @param {*} modal
* @param {*} record
* @param {*} modal
* @param {*} record
*/
onOpen(modal, record) {
modal.current.open({
record
record,
})
}
/**
* 对表格上的操作进行统一处理
* [异步]
* @param {*} action
* @param {*} successMessage
* @param {*} action
* @param {*} successMessage
*/
async onAction(action, successMessage) {
this.table.current.onLoading()
@@ -166,13 +183,10 @@ export default class index extends Component {
/**
* 删除
* @param {*} record
* @param {*} record
*/
onDelete(record) {
this.onAction(
apiAction.delete(record),
'删除成功'
)
this.onAction(apiAction.delete(record), '删除成功')
}
//#region 自定义方法
@@ -202,7 +216,9 @@ export default class index extends Component {
<Button
icon={<AntIcon type="plus" />}
onClick={() => this.onOpen(this.addForm)}
>新增{name}</Button>
>
新增{name}
</Button>
</Auth>
}
/>

View File

@@ -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 (
<Form ref={this.form} className="yo-form">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
<div className="yo-form-group">
<Form.Item label="选择机构" name="grantOrgIdList">
<TreeSelect
showCheckedStrategy="SHOW_PARENT"
treeData={this.state.options.orgData}
placeholder="请选择机构"
treeCheckable
/>
</Form.Item>
<Form.Item label="选择区域" name="grantAreaCodeList">
<TreeSelect
showCheckedStrategy="SHOW_PARENT"
treeData={this.state.options.areaData}
placeholder="请选择所属区域"
treeCheckable
/>
</Form.Item>
</div>
</Spin>
</Form>
)
}
}

View File

@@ -1,5 +1,17 @@
import React, { Component } from 'react'
import { Button, Row, Col, Form, Input, DatePicker, Radio, Table, Select, Spin, TreeSelect } from 'antd'
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'
@@ -9,25 +21,24 @@ import moment from 'moment'
const initialValues = {
sex: 0,
sysEmpParam: {}
sysEmpParam: {},
}
export default class form extends Component {
state = {
// 加载状态
loading: true,
codes: {
orgType: []
orgType: [],
},
options: {
orgData: [],
posData: []
posData: [],
},
sysEmpParam: {
extIds: []
}
extIds: [],
},
}
extColumns = [
{
@@ -45,7 +56,7 @@ export default class form extends Component {
placeholder="请选择附加组织机构"
/>
</Form.Item>
)
),
},
{
title: '附属岗位',
@@ -56,33 +67,28 @@ export default class form extends Component {
<Select
defaultValue={text}
className="w-100-p"
placeholder="请选择附加职位信息">
{
this.state.options.posData.map(item => {
return <Select.Option
key={item.id}
value={item.id}
>
{item.name}</Select.Option>
})
}
placeholder="请选择附加职位信息"
>
{this.state.options.posData.map(item => {
return (
<Select.Option key={item.id} value={item.id}>
{item.name}
</Select.Option>
)
})}
</Select>
</Form.Item>
)
),
},
{
title: '操作',
key: 'action',
width: '70px',
render: (text, record) => (
<Button
onClick={() => this.onRemoveExtData(record)}
size="small"
danger
>
<Button onClick={() => this.onRemoveExtData(record)} size="small" danger>
删除
</Button>
)
),
},
]
// 表单实例
@@ -102,11 +108,13 @@ export default class form extends Component {
* 填充数据
* 可以在设置this.record之后对其作出数据结构调整
* [异步,必要]
* @param {*} params
* @param {*} params
*/
async fillData(params) {
this.record = cloneDeep(params.record || {})
//#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')
@@ -118,17 +126,17 @@ export default class form extends Component {
// 提交的时候是"param",而获取下来却是"info",在这里转换一下
if (this.record.sysEmpInfo) {
this.record.sysEmpParam = this.record.sysEmpInfo;
delete 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);
this.record.sysEmpParam.posIdList = this.record.sysEmpParam.positions.map(p => p.posId)
}
// 附加信息
@@ -138,12 +146,12 @@ export default class form extends Component {
key: i,
orgId: p.orgId,
posId: p.posId,
};
});
}
})
}
if (params.orgId) {
this.record.sysEmpParam.orgId = params.orgId;
this.record.sysEmpParam.orgId = params.orgId
}
this.setState({
@@ -154,18 +162,18 @@ export default class form extends Component {
posData,
},
sysEmpParam: {
...this.record.sysEmpParam
}
...this.record.sysEmpParam,
},
})
this.record = {
...this.record
...this.record,
}
//#endregion
this.form.current.setFieldsValue(this.record)
this.setState({
loading: false
loading: false,
})
}
@@ -173,7 +181,7 @@ export default class form extends Component {
* 获取数据
* 可以对postData进行数据结构调整
* [异步,必要]
* @returns
* @returns
*/
async getData() {
const form = this.form.current
@@ -206,24 +214,26 @@ export default class form extends Component {
const record = {
key: extIds.length > 0 ? extIds[extIds.length - 1].key + 1 : 0,
orgId: undefined,
posId: undefined
posId: undefined,
}
this.setState({
sysEmpParam: {
extIds: [...extIds, record]
this.setState(
{
sysEmpParam: {
extIds: [...extIds, record],
},
},
() => {
console.log(this.form.current.getFieldsValue())
}
}, () => {
console.log(this.form.current.getFieldsValue())
})
)
}
onRemoveExtData(record) {
const ext = this.state.sysEmpParam.extIds,
remove = ext.find((p) => p.key === record.key),
index = ext.indexOf(remove);
remove = ext.find(p => p.key === record.key),
index = ext.indexOf(remove)
ext.splice(index, 1);
ext.splice(index, 1)
console.log(ext)
// this.form.current.setFieldsValue({
@@ -232,14 +242,16 @@ export default class form extends Component {
// }
// })
this.setState({
sysEmpParam: {
extIds: ext
this.setState(
{
sysEmpParam: {
extIds: ext,
},
},
() => {
//console.log(this.form.current.getFieldsValue())
}
}, () => {
//console.log(this.form.current.getFieldsValue())
})
)
}
//#endregion
renderExtInfoTable() {
@@ -251,79 +263,100 @@ export default class form extends Component {
pagination={false}
size="small"
bordered
rowKey={(record) => record.key}
footer={
() =>
<Button
block
icon={
<AntIcon type="plus" />
}
type="dashed"
onClick={() => this.onAddExtData()}>
新增一项</Button>
}
>
</Table>
rowKey={record => record.key}
footer={() => (
<Button
block
icon={<AntIcon type="plus" />}
type="dashed"
onClick={() => this.onAddExtData()}
>
新增一项
</Button>
)}
></Table>
)
}
render() {
return (
<Form
initialValues={initialValues}
ref={this.form}
className="yo-form"
>
<Spin
spinning={this.state.loading}
indicator={<AntIcon type="loading" />}>
<Form initialValues={initialValues} ref={this.form} className="yo-form">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
<h3 className="h3">基本信息</h3>
<div className="yo-form-group">
<Form.Item label="账号" name="account" rules={[{ required: true, message: '请输入账号', trigger: 'blur' }]}>
<Form.Item
label="账号"
name="account"
rules={[{ required: true, message: '请输入账号', trigger: 'blur' }]}
>
<Input autoComplete="off" placeholder="请输入账号" />
</Form.Item>
<Form.Item label="姓名" name="name" rules={[{ required: true, message: '请输入姓名', trigger: 'blur' }]}>
<Form.Item
label="姓名"
name="name"
rules={[{ required: true, message: '请输入姓名', trigger: 'blur' }]}
>
<Input autoComplete="off" placeholder="请输入姓名" />
</Form.Item>
{this.props.mode == 'add' && <>
<Form.Item label="密码" name="password" rules={[{ required: true, message: '请输入密码', trigger: 'blur' }]}>
<Input.Password autoComplete="off" placeholder="请输入密码" />
</Form.Item>
<Form.Item label="确认密码" name="confirm" rules={[{ required: true, message: '请确认密码', trigger: 'blur' }]}>
<Input.Password autoComplete="off" placeholder="请确认密码" />
</Form.Item>
</>
}
<Form.Item label="昵称" name="nickName" >
{this.props.mode == 'add' && (
<>
<Form.Item
label="密码"
name="password"
rules={[
{ required: true, message: '请输入密码', trigger: 'blur' },
]}
>
<Input.Password autoComplete="off" placeholder="请输入密码" />
</Form.Item>
<Form.Item
label="确认密码"
name="confirm"
rules={[
{ required: true, message: '请确认密码', trigger: 'blur' },
]}
>
<Input.Password autoComplete="off" placeholder="请确认密码" />
</Form.Item>
</>
)}
<Form.Item label="昵称" name="nickName">
<Input autoComplete="off" placeholder="请输入昵称" />
</Form.Item>
<Form.Item label="生日" name="birthday" >
<Form.Item label="生日" name="birthday">
<DatePicker className="w-100-p" />
</Form.Item>
<Form.Item label="性别" name="sex" >
<Radio.Group >
<Form.Item label="性别" name="sex">
<Radio.Group>
<Radio.Button value={0}>
<AntIcon className="mr-xxs" type="stop" />
<span>保密</span>
</Radio.Button>
<Radio.Button value={1}>
<AntIcon style={{ color: '#1890ff' }} className="mr-xxs" type="man" />
<AntIcon
style={{ color: '#1890ff' }}
className="mr-xxs"
type="man"
/>
<span></span>
</Radio.Button>
<Radio.Button value={2}>
<AntIcon style={{ color: '#eb2f96' }} className="mr-xxs" type="woman" />
<AntIcon
style={{ color: '#eb2f96' }}
className="mr-xxs"
type="woman"
/>
<span></span>
</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label="邮箱" name="email" >
<Form.Item label="邮箱" name="email">
<Input autoComplete="off" placeholder="请输入邮箱" />
</Form.Item>
<Form.Item label="手机号" name="phone" >
<Form.Item label="手机号" name="phone">
<Input autoComplete="off" placeholder="请输入手机号" />
</Form.Item>
<Form.Item label="电话" name="tel" >
<Form.Item label="电话" name="tel">
<Input autoComplete="off" placeholder="请输入电话" />
</Form.Item>
</div>
@@ -332,7 +365,8 @@ export default class form extends Component {
<Form.Item
label="所属组织机构"
name={['sysEmpParam', 'orgId']}
rules={[{ required: true, message: '所属组织机构' }]}>
rules={[{ required: true, message: '所属组织机构' }]}
>
<TreeSelect
treeData={this.state.options.orgData}
dropdownStyle={{ maxHeight: '300px', overflow: 'auto' }}
@@ -340,26 +374,18 @@ export default class form extends Component {
placeholder="请选择所属组织机构"
/>
</Form.Item>
<Form.Item
label="工号"
name={['sysEmpParam', 'jobNum']} >
<Input
autoComplete="off"
placeholder="请输入工号" />
<Form.Item label="工号" name={['sysEmpParam', 'jobNum']}>
<Input autoComplete="off" placeholder="请输入工号" />
</Form.Item>
<Form.Item label="职位信息" name={['sysEmpParam', 'posIdList']}>
<Select
mode="multiple"
placeholder="请选择职位信息">
{
this.state.options.posData.map(item => {
return <Select.Option
key={item.id}
value={item.id}
>
{item.name}</Select.Option>
})
}
<Select mode="multiple" placeholder="请选择职位信息">
{this.state.options.posData.map(item => {
return (
<Select.Option key={item.id} value={item.id}>
{item.name}
</Select.Option>
)
})}
</Select>
</Form.Item>
</div>

View File

@@ -1,11 +1,27 @@
import React, { Component } from 'react'
import { Button, Card, Descriptions, Form, Input, List, message as Message, Popconfirm, Select, Switch } from 'antd'
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 = {
@@ -16,19 +32,21 @@ const apiAction = {
delete: api.sysUserDelete,
changeStatus: api.sysUserChangeStatus,
resetPwd: api.sysUserResetPwd
resetPwd: api.sysUserResetPwd,
grantRole: api.sysUserGrantRole,
grantData: api.sysUserGrantData,
}
// 用于弹窗标题
const name = '用户'
export default class index extends Component {
state = {
codes: {
sex: [],
commonStatus: []
}
commonStatus: [],
},
}
// 表格实例
@@ -39,6 +57,8 @@ export default class index extends Component {
// 编辑窗口实例
editForm = React.createRef()
roleForm = React.createRef()
dataForm = React.createRef()
// 树选中节点
selectId = undefined
@@ -46,9 +66,9 @@ export default class index extends Component {
* 阻止外部组件引发的渲染,提升性能
* 可自行添加渲染条件
* [必要]
* @param {*} props
* @param {*} state
* @returns
* @param {*} props
* @param {*} state
* @returns
*/
shouldComponentUpdate(props, state) {
return !isEqual(this.state, state)
@@ -61,28 +81,30 @@ export default class index extends Component {
componentDidMount() {
this.list.current.onLoading()
getDictData('sex', 'common_status').then(res => {
this.setState({
codes: res
}, () => {
this.list.current.onLoadData()
})
this.setState(
{
codes: res,
},
() => {
this.list.current.onLoadData()
}
)
})
}
/**
* 调用加载数据接口,可在调用前对query进行处理
* [异步,必要]
* @param {*} params
* @param {*} query
* @returns
* @param {*} params
* @param {*} query
* @returns
*/
loadData = async (params, query) => {
query = {
...query,
sysEmpParam: {
orgId: this.selectId
}
orgId: this.selectId,
},
}
const { data } = await apiAction.page({
@@ -105,7 +127,7 @@ export default class index extends Component {
/**
* 树节点选中事件
* [必要]
* @param {*} id
* @param {*} id
*/
onSelectTree(id) {
this.selectId = id
@@ -114,15 +136,15 @@ export default class index extends Component {
/**
* 绑定字典数据
* @param {*} code
* @param {*} name
* @returns
* @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)
const c = codes.find(p => p.code == code)
if (c) {
return c.value
}
@@ -132,21 +154,21 @@ export default class index extends Component {
/**
* 打开新增/编辑弹窗
* @param {*} modal
* @param {*} record
* @param {*} modal
* @param {*} id
*/
onOpen(modal, record) {
onOpen(modal, id) {
modal.current.open({
orgId: this.selectId,
record
id,
})
}
/**
* 对表格上的操作进行统一处理
* [异步]
* @param {*} action
* @param {*} successMessage
* @param {*} action
* @param {*} successMessage
*/
async onAction(action, successMessage) {
this.list.current.onLoading()
@@ -161,39 +183,63 @@ export default class index extends Component {
/**
* 删除
* @param {*} record
* @param {*} record
*/
onDelete(record) {
this.onAction(
apiAction.delete(record),
'删除成功'
)
this.onAction(apiAction.delete(record), '删除成功')
}
//#region 自定义方法
renderItem(record) {
const { id, account, name, nickName, avatar, sex, phone, email, status } = record
return (
<List.Item
key={record.id}
actions={
[
<Auth auth="sysUser:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a>
</Auth>,
<Auth auth="sysOrg:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(record)}
>
<a>删除</a>
</Popconfirm>
</Auth>,
<Auth aut="sysUser:resetPwd">
<a onClick={() => this.onResetPassword(record)}>重置密码</a>
</Auth>
]
}
key={id}
actions={[
<Auth auth="sysUser:edit">
<a onClick={() => this.onOpen(this.editForm, id)}>编辑</a>
</Auth>,
<Auth auth="sysOrg:delete">
<Popconfirm
placement="topRight"
title="是否确认删除"
onConfirm={() => this.onDelete(id)}
>
<a>删除</a>
</Popconfirm>
</Auth>,
<Auth aut="sysUser:resetPwd">
<a onClick={() => this.onResetPassword(id)}>重置密码</a>
</Auth>,
<Auth auth={{ sysUser: [['grantRole'], ['grantData']] }}>
<Dropdown
placement="bottomRight"
overlay={
<Menu>
{auth('sysUser:grantRole') && (
<Menu.Item key="1">
<a onClick={() => this.onOpen(this.roleForm, id)}>
授权角色
</a>
</Menu.Item>
)}
{auth('sysUser:grantData') && (
<Menu.Item key="2">
<a onClick={() => this.onOpen(this.dataForm, id)}>
授权额外数据
</a>
</Menu.Item>
)}
</Menu>
}
>
<a className="ant-dropdown-link">
授权
<AntIcon type="down" />
</a>
</Dropdown>
</Auth>,
]}
>
<List.Item.Meta
avatar={
@@ -201,48 +247,42 @@ export default class index extends Component {
type="avatar"
shape="square"
size={48}
id={record.avatar}
id={avatar}
icon={<AntIcon type="user" />}
/>
}
title={record.nickName || record.name}
description={record.account}
title={nickName || name}
description={account}
/>
<Descriptions className="flex-1" column={2}>
<Descriptions.Item label="性别">{this.bindCodeValue(record.sex, 'sex')}</Descriptions.Item>
<Descriptions.Item label="手机">{record.phone || '未设置'}</Descriptions.Item>
<Descriptions.Item label="邮箱">{record.email || '未设置'}</Descriptions.Item>
<Descriptions.Item label="性别">
{this.bindCodeValue(sex, 'sex')}
</Descriptions.Item>
<Descriptions.Item label="手机">{phone || '未设置'}</Descriptions.Item>
<Descriptions.Item label="邮箱">{email || '未设置'}</Descriptions.Item>
</Descriptions>
<div className="yo-list-content--h">
<Auth auth="sysUser:changeStatus">
<div className="yo-list-content--h--item text-center">
<Switch
checked={!record.status}
loading={record.statusChanging}
checked={!status}
checkedChildren={this.bindCodeValue(0, 'common_status')}
unCheckedChildren={this.bindCodeValue(1, 'common_status')}
onChange={checked => this.onSetUserStatus(record, checked)}
onChange={checked => this.onSetUserStatus(id, checked)}
/>
</div>
</Auth>
</div>
</List.Item>
)
}
onSetUserStatus(record, checked) {
this.onAction(
apiAction.changeStatus({ id: record.id, status: +!checked }),
'设置成功'
)
onSetUserStatus(id, checked) {
this.onAction(apiAction.changeStatus({ id, status: +!checked }), '设置成功')
}
onResetPassword(record) {
this.onAction(
apiAction.resetPwd(record),
'重置成功'
)
onResetPassword(id) {
this.onAction(apiAction.resetPwd({ id }), '重置成功')
}
//#endregion
@@ -251,7 +291,7 @@ export default class index extends Component {
<QueryTreeLayout
loadData={this.loadTreeData}
defaultExpanded={true}
onSelect={(key) => this.onSelectTree(key)}
onSelect={key => this.onSelectTree(key)}
>
<Container mode="fluid">
<Card bordered={false}>
@@ -273,14 +313,13 @@ export default class index extends Component {
placeholder="请选择状态"
className="w-200"
>
{
this.state.codes.commonStatus.map(item => {
return <Select.Option
key={item.code}
item={item.code}
>{item.value}</Select.Option>
})
}
{this.state.codes.commonStatus.map(item => {
return (
<Select.Option key={item.code} item={item.code}>
{item.value}
</Select.Option>
)
})}
</Select>
</Form.Item>
</Auth>
@@ -289,9 +328,11 @@ export default class index extends Component {
<Button
icon={<AntIcon type="plus" />}
onClick={() => this.onOpen(this.addForm)}
>新增{name}</Button>
>
新增{name}
</Button>
}
renderItem={(record) => this.renderItem(record)}
renderItem={record => this.renderItem(record)}
/>
</Card>
</Container>
@@ -313,6 +354,24 @@ export default class index extends Component {
>
<FormBody mode="edit" />
</ModalForm>
<ModalForm
title="角色授权"
action={apiAction.grantRole}
ref={this.roleForm}
onSuccess={() => this.list.current.onReloadData()}
>
<RoleForm />
</ModalForm>
<ModalForm
title="数据授权"
action={apiAction.grantData}
ref={this.dataForm}
onSuccess={() => this.list.current.onReloadData()}
>
<DataForm />
</ModalForm>
</QueryTreeLayout>
)
}

View File

@@ -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 (
<Form ref={this.form} className="yo-form">
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
<div className="yo-form-group">
<Form.Item label="请选择角色" name="grantRoleIdList">
<Select mode="multiple" placeholder="请选择角色">
{this.state.options.roleData.map(item => {
return (
<Select.Option key={item.id} value={item.id}>
{item.name}
</Select.Option>
)
})}
</Select>
</Form.Item>
</div>
</Spin>
</Form>
)
}
}

View File

@@ -1,6 +1,21 @@
const layout = (state = {
siderCollapsed: false
}, action) => {
import { SETTING_KEY } from "common/storage"
import { SIDER_BREAK_POINT } from "util/global"
const defaultState = {
siderCollapsed: false,
allowSiderCollapsed: true
}
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':
@@ -13,8 +28,23 @@ const layout = (state = {
return state
// 侧边收起状态
case 'TOGGLE_COLLAPSED':
const _state = { ...state, siderCollapsed: action.siderCollapsed }
return _state
{
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
}
default:
return state
}

View File

@@ -36,4 +36,9 @@ export const RSA_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQU
/**
* 城市名称
*/
export const CITY = '黄石市'
export const CITY = '黄石市'
/**
* 响应式响应宽度
*/
export const SIDER_BREAK_POINT = 1366

View File

@@ -1,3 +1,41 @@
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) => {
@@ -38,4 +76,32 @@ export const getSearchInfo = ({ query, queryType }) => {
})
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: '='
}

View File

@@ -20,7 +20,6 @@ class ComponentDynamic extends Component {
if (this.props.onRef) {
this.props.onRef(this)
}
return true
}
@@ -73,6 +72,40 @@ class ComponentDynamic extends Component {
}
}
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 <iframe ref="content" title={title} />
}
}
export default class index extends Component {
state = {
actived: '',
@@ -113,13 +146,15 @@ export default class index extends Component {
render() {
this.panes = []
const { actived } = this.state
return (
<Layout.Content>
<div className="yo-tab-external-mount">
<Tabs
type="editable-card"
hideAdd
activeKey={this.state.actived}
activeKey={actived}
onChange={activeKey => this.onChange(activeKey)}
onEdit={(targetKey, action) => this.onClose(targetKey, action)}
>
@@ -183,19 +218,29 @@ export default class index extends Component {
<div
key={pane.key}
className={
(pane.key === this.state.actived
(pane.key === actived
? 'yo-tab-external-tabpane-active'
: 'yo-tab-external-tabpane-inactive') +
' yo-tab-external-tabpane'
}
>
<ComponentDynamic
path={pane.path}
id={pane.key}
key={pane.key}
param={pane.param}
onRef={p => this.panes.push(p)}
/>
{pane.openType === 1 ? (
<ComponentDynamic
path={pane.path}
id={pane.key}
key={pane.key}
param={pane.param}
paneActived={pane.key === actived}
onRef={p => this.panes.push(p)}
/>
) : pane.openType === 2 ? (
<Iframe
src={pane.path}
title={pane.key}
id={pane.key}
onRef={p => this.panes.push(p)}
/>
) : null}
</div>
)
})}

View File

@@ -33,13 +33,20 @@ export default class index extends Component {
}
render() {
const { allowSiderCollapsed } = this.state
return (
<Layout.Header>
<Container mode="fluid">
<div className="header-actions">
<span className="header-action mr-md" onClick={() => this.onCollapsed()}>
<AntIcon type="menu" />
</span>
{allowSiderCollapsed && (
<span
className="header-action mr-md"
onClick={() => this.onCollapsed()}
>
<AntIcon type="menu" />
</span>
)}
<Logo />
<Search />
</div>

View File

@@ -78,7 +78,7 @@ export default class search extends Component {
}
onSelect(value, option) {
const { id, meta, component } = option.menu
const { id, meta, component, link, redirect, openType } = option.menu
// 选中时清空输入框内容,并失去焦点
this.setState({ searchValue: '' })
@@ -88,7 +88,19 @@ export default class search extends Component {
key: id,
title: meta.title,
icon: meta.icon,
path: component,
path: (() => {
switch (openType) {
case 1:
return component
case 2:
return link
case 3:
return redirect
default:
return null
}
})(),
openType,
})
}

View File

@@ -83,7 +83,7 @@ class User extends Component {
)}
{user.roles &&
user.roles.map(role => (
<Tag color="purple" className="mb-xs">
<Tag key={role.id} color="purple" className="mb-xs">
{role.name}
</Tag>
))}

View File

@@ -9,8 +9,7 @@ Swiper.use([Mousewheel, Scrollbar])
const { getState, subscribe } = store
let timer,
swiper
let timer, swiper
const siderSwiperOptions = {
direction: 'vertical',
@@ -31,17 +30,15 @@ const UpdateSwiper = () => {
}, 300)
}
export default class index extends Component {
state = {
...getState('layout')
...getState('layout'),
}
constructor(props) {
super(props)
this.unsubscribe = subscribe('layout', (state) => {
this.unsubscribe = subscribe('layout', state => {
this.setState(state)
})
}
@@ -78,7 +75,10 @@ export default class index extends Component {
<div className="swiper-container" id="layout--swiper-container">
<div className="swiper-wrapper">
<div className="swiper-slide">
<Menu parent={this} menuStyle={{ height: '100%', borderRight: 0 }} />
<Menu
parent={this}
menuStyle={{ height: '100%', borderRight: 0 }}
/>
</div>
</div>
<div className="swiper-scrollbar" id="layout--swiper-scrollbar"></div>

View File

@@ -6,9 +6,8 @@ import store from 'store'
const { getState, subscribe } = store
export default class index extends Component {
state = {
...getState('nav')
...getState('nav'),
}
constructor(props) {
@@ -23,21 +22,25 @@ export default class index extends Component {
this.unsubscribe()
}
renderMenu = (menu) => {
return menu.map((p) => {
renderMenu = menu => {
return menu.map(p => {
return p.children ? this.renderSubMenu(p) : this.renderMenuItem(p)
})
}
renderSubMenu = (menu) => {
renderSubMenu = menu => {
return (
<Menu.SubMenu key={menu.id} title={menu.meta.title} icon={menu.meta.icon && <AntIcon type={menu.meta.icon} />}>
<Menu.SubMenu
key={menu.id}
title={menu.meta.title}
icon={menu.meta.icon && <AntIcon type={menu.meta.icon} />}
>
{this.renderMenu(menu.children)}
</Menu.SubMenu>
)
}
renderMenuItem = (menu) => {
renderMenuItem = menu => {
return (
<Menu.Item key={menu.id} onClick={() => this.onOpenContentWindow(menu)}>
{menu.meta.icon && <AntIcon type={menu.meta.icon} />}
@@ -46,12 +49,26 @@ export default class index extends Component {
)
}
onOpenContentWindow = (menu) => {
onOpenContentWindow = menu => {
const { id, meta, component, link, redirect, openType } = menu
window.openContentWindow({
key: menu.id,
title: menu.meta.title,
icon: menu.meta.icon,
path: menu.component
key: id,
title: meta.title,
icon: meta.icon,
path: (() => {
switch (openType) {
case 1:
return component
case 2:
return link
case 3:
return redirect
default:
return null
}
})(),
openType,
})
}
@@ -60,12 +77,11 @@ export default class index extends Component {
}
render() {
const props = {
mode: 'inline',
selectable: false,
style: this.props.menuStyle,
theme: 'light'
theme: 'light',
}
const on = {
@@ -74,16 +90,20 @@ export default class index extends Component {
return (
<>
{
this.state.nav.map((item, i) => {
{this.state.nav.map((item, i) => {
if (item.menu.length) {
return (
<section key={i}>
<div className="yo-sider-nav--app">{item.app.name}</div>
<Menu {...props} {...on}>{this.renderMenu(item.menu)}</Menu>
<Menu {...props} {...on}>
{this.renderMenu(item.menu)}
</Menu>
</section>
)
})
}
} else {
return false
}
})}
</>
)
}

View File

@@ -1,10 +1,9 @@
import React, { Component } from 'react'
import { Spin, Layout } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { api } from 'common/api'
import { cloneDeep, groupBy, findIndex, last } from 'lodash'
import store from 'store'
import { EMPTY_ID } from 'util/global'
import { EMPTY_ID, SIDER_BREAK_POINT } from 'util/global'
import Header from './_layout/header'
import Sider from './_layout/sider'
@@ -83,6 +82,9 @@ export default class index extends Component {
})
})
})
window.addEventListener('resize', this.onResizeSider)
this.onResizeSider()
}
/**
@@ -103,7 +105,31 @@ export default class index extends Component {
return
}
const path = settings.path.startsWith('/') ? settings.path : `/${settings.path}`
let path = (p => {
if (p.startsWith('http')) return p
if (p.startsWith('/')) return p
else return `/${p}`
})(settings.path)
if ([2, 3].includes(settings.openType)) {
const param = (p => {
const arr = []
if (p && p.constructor === Object) {
Object.keys(p).forEach(key => {
arr.push(`${key}=${(p[key] || '').toString()}`)
})
}
return arr.join('&')
})(settings.param)
path += param ? `?${param}` : ''
if (settings.openType === 3) {
// 打开新的浏览器窗口
window.open(path)
return
}
}
/**
* 向标签页队列中添加一个新的标签页
@@ -118,7 +144,9 @@ export default class index extends Component {
path,
param: settings.param,
loaded: false,
openType: settings.openType || 1,
}
this.setState({
panes: [...this.state.panes, newPane],
})
@@ -231,6 +259,13 @@ export default class index extends Component {
})
}
onResizeSider = () => {
dispatch({
type: 'AUTO_TOGGLE_COLLAPSED',
siderCollapsed: window.innerWidth <= SIDER_BREAK_POINT,
})
}
render() {
const { loading, panes, actived } = this.state

View File

@@ -1315,6 +1315,11 @@
dependencies:
"@hapi/hoek" "^8.3.0"
"@icons/material@^0.2.4":
version "0.2.4"
resolved "https://registry.nlark.com/@icons/material/download/@icons/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
integrity sha1-6QyfcXaLNzbnbX3WeD/Gwq+oi8g=
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.nlark.com/@istanbuljs/load-nyc-config/download/@istanbuljs/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -2554,7 +2559,7 @@ arrify@^2.0.1:
resolved "https://registry.nlark.com/arrify/download/arrify-2.0.1.tgz?cache=0&sync_timestamp=1619599497996&other_urls=https%3A%2F%2Fregistry.nlark.com%2Farrify%2Fdownload%2Farrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
integrity sha1-yWVekzHgq81YjSp8rX6ZVvZnAfo=
asap@~2.0.6:
asap@~2.0.3, asap@~2.0.6:
version "2.0.6"
resolved "https://registry.nlark.com/asap/download/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
@@ -2852,6 +2857,11 @@ balanced-match@^1.0.0:
resolved "https://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.2.tgz?cache=0&sync_timestamp=1617714233441&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbalanced-match%2Fdownload%2Fbalanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha1-6D46fj8wCzTLnYf2FfoMvzV2kO4=
base16@^1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/base16/download/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=
base64-js@^1.0.2:
version "1.5.1"
resolved "https://registry.nlark.com/base64-js/download/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@@ -3752,6 +3762,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
cross-fetch@^3.0.4:
version "3.1.4"
resolved "https://registry.npm.taobao.org/cross-fetch/download/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
integrity sha1-lyPzo6JHv4uJA586OAqSROj6Lzk=
dependencies:
node-fetch "2.6.1"
cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.npm.taobao.org/cross-spawn/download/cross-spawn-7.0.3.tgz?cache=0&sync_timestamp=1606748073153&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcross-spawn%2Fdownload%2Fcross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -5086,6 +5103,31 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"
fbemitter@^3.0.0:
version "3.0.0"
resolved "https://registry.npm.taobao.org/fbemitter/download/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3"
integrity sha1-ALKhr1QRJUqrQWzXX55iib7kv/M=
dependencies:
fbjs "^3.0.0"
fbjs-css-vars@^1.0.0:
version "1.0.2"
resolved "https://registry.npm.taobao.org/fbjs-css-vars/download/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8"
integrity sha1-IWVRE2rgL+JVkyw+yHdfGOLAeLg=
fbjs@^3.0.0:
version "3.0.0"
resolved "https://registry.npm.taobao.org/fbjs/download/fbjs-3.0.0.tgz?cache=0&sync_timestamp=1602048886093&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffbjs%2Fdownload%2Ffbjs-3.0.0.tgz#0907067fb3f57a78f45d95f1eacffcacd623c165"
integrity sha1-CQcGf7P1enj0XZXx6s/8rNYjwWU=
dependencies:
cross-fetch "^3.0.4"
fbjs-css-vars "^1.0.0"
loose-envify "^1.0.0"
object-assign "^4.1.0"
promise "^7.1.1"
setimmediate "^1.0.5"
ua-parser-js "^0.7.18"
figgy-pudding@^3.5.1:
version "3.5.2"
resolved "https://registry.nlark.com/figgy-pudding/download/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
@@ -5212,6 +5254,14 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
flux@^4.0.1:
version "4.0.1"
resolved "https://registry.npm.taobao.org/flux/download/flux-4.0.1.tgz#7843502b02841d4aaa534af0b373034a1f75ee5c"
integrity sha1-eENQKwKEHUqqU0rws3MDSh917lw=
dependencies:
fbemitter "^3.0.0"
fbjs "^3.0.0"
follow-redirects@^1.0.0, follow-redirects@^1.10.0:
version "1.14.1"
resolved "https://registry.nlark.com/follow-redirects/download/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
@@ -7127,6 +7177,11 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
lodash-es@^4.17.15:
version "4.17.21"
resolved "https://registry.npm.taobao.org/lodash-es/download/lodash-es-4.17.21.tgz?cache=0&sync_timestamp=1613836185353&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash-es%2Fdownload%2Flodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
integrity sha1-Q+YmxG5lkbd1C+srUBFzkMYJ4+4=
lodash._reinterpolate@^3.0.0:
version "3.0.0"
resolved "https://registry.nlark.com/lodash._reinterpolate/download/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@@ -7137,11 +7192,21 @@ lodash.clonedeep@^4.5.0:
resolved "https://registry.nlark.com/lodash.clonedeep/download/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
lodash.curry@^4.0.1:
version "4.1.1"
resolved "https://registry.npm.taobao.org/lodash.curry/download/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA=
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.npm.taobao.org/lodash.debounce/download/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
lodash.flow@^3.3.0:
version "3.5.0"
resolved "https://registry.npm.taobao.org/lodash.flow/download/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.nlark.com/lodash.memoize/download/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -7177,7 +7242,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.npm.taobao.org/lodash.uniq/download/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0:
"lodash@>=3.5 <5", lodash@^4.0.1, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.nlark.com/lodash/download/lodash-4.17.21.tgz?cache=0&sync_timestamp=1618847150612&other_urls=https%3A%2F%2Fregistry.nlark.com%2Flodash%2Fdownload%2Flodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha1-Z5WRxWTDv/quhFTPCz3zcMPWkRw=
@@ -7261,6 +7326,11 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
material-colors@^1.2.1:
version "1.2.6"
resolved "https://registry.npm.taobao.org/material-colors/download/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
integrity sha1-bRlYhxEmmSzuzHL0vMTY8BCGX0Y=
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.npm.taobao.org/md5.js/download/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@@ -7639,6 +7709,11 @@ no-case@^3.0.4:
lower-case "^2.0.2"
tslib "^2.0.3"
node-fetch@2.6.1:
version "2.6.1"
resolved "https://registry.nlark.com/node-fetch/download/node-fetch-2.6.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fnode-fetch%2Fdownload%2Fnode-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha1-BFvTI2Mfdu0uK1VXM5RBa2OaAFI=
node-forge@^0.10.0:
version "0.10.0"
resolved "https://registry.npm.taobao.org/node-forge/download/node-forge-0.10.0.tgz?cache=0&sync_timestamp=1599010726129&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-forge%2Fdownload%2Fnode-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
@@ -9040,6 +9115,13 @@ promise-inflight@^1.0.1:
resolved "https://registry.nlark.com/promise-inflight/download/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
promise@^7.1.1:
version "7.3.1"
resolved "https://registry.npm.taobao.org/promise/download/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
integrity sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=
dependencies:
asap "~2.0.3"
promise@^8.1.0:
version "8.1.0"
resolved "https://registry.npm.taobao.org/promise/download/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e"
@@ -9063,7 +9145,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.npm.taobao.org/prop-types/download/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha1-UsQedbjIfnK52TYOAga5ncv/psU=
@@ -9142,6 +9224,11 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.nlark.com/punycode/download/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha1-tYsBCsQMIsVldhbI0sLALHv0eew=
pure-color@^1.2.0:
version "1.3.0"
resolved "https://registry.npm.taobao.org/pure-color/download/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=
q@^1.1.2:
version "1.5.1"
resolved "https://registry.nlark.com/q/download/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@@ -9576,6 +9663,29 @@ react-app-polyfill@^2.0.0:
regenerator-runtime "^0.13.7"
whatwg-fetch "^3.4.1"
react-base16-styling@^0.6.0:
version "0.6.0"
resolved "https://registry.npm.taobao.org/react-base16-styling/download/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c"
integrity sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw=
dependencies:
base16 "^1.0.0"
lodash.curry "^4.0.1"
lodash.flow "^3.3.0"
pure-color "^1.2.0"
react-color@^2.19.3:
version "2.19.3"
resolved "https://registry.npm.taobao.org/react-color/download/react-color-2.19.3.tgz#ec6c6b4568312a3c6a18420ab0472e146aa5683d"
integrity sha1-7GxrRWgxKjxqGEIKsEcuFGqlaD0=
dependencies:
"@icons/material" "^0.2.4"
lodash "^4.17.15"
lodash-es "^4.17.15"
material-colors "^1.2.1"
prop-types "^15.5.10"
reactcss "^1.2.0"
tinycolor2 "^1.4.1"
react-dev-utils@^11.0.3:
version "11.0.4"
resolved "https://registry.npm.taobao.org/react-dev-utils/download/react-dev-utils-11.0.4.tgz?cache=0&sync_timestamp=1615231838520&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-dev-utils%2Fdownload%2Freact-dev-utils-11.0.4.tgz#a7ccb60257a1ca2e0efe7a83e38e6700d17aa37a"
@@ -9630,6 +9740,21 @@ react-is@^17.0.1:
resolved "https://registry.nlark.com/react-is/download/react-is-17.0.2.tgz?cache=0&sync_timestamp=1623273254569&other_urls=https%3A%2F%2Fregistry.nlark.com%2Freact-is%2Fdownload%2Freact-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha1-5pHUqOnHiTZWVVOas3J2Kw77VPA=
react-json-view@^1.21.3:
version "1.21.3"
resolved "https://registry.npm.taobao.org/react-json-view/download/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475"
integrity sha1-8YQgnujxvzdPsMQbCBPP9UVJxHU=
dependencies:
flux "^4.0.1"
react-base16-styling "^0.6.0"
react-lifecycles-compat "^3.0.4"
react-textarea-autosize "^8.3.2"
react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.npm.taobao.org/react-lifecycles-compat/download/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha1-TxonOv38jzSIqMUWv9p4+HI1I2I=
react-monaco-editor@^0.43.0:
version "0.43.0"
resolved "https://registry.npm.taobao.org/react-monaco-editor/download/react-monaco-editor-0.43.0.tgz#495578470db7b27ab306af813b31f206a6bf9d1c"
@@ -9738,6 +9863,15 @@ react-scripts@4.0.3:
optionalDependencies:
fsevents "^2.1.3"
react-textarea-autosize@^8.3.2:
version "8.3.3"
resolved "https://registry.nlark.com/react-textarea-autosize/download/react-textarea-autosize-8.3.3.tgz?cache=0&sync_timestamp=1622628433420&other_urls=https%3A%2F%2Fregistry.nlark.com%2Freact-textarea-autosize%2Fdownload%2Freact-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8"
integrity sha1-9wkTlFNp2kU/1VTBaPa6rNH6BNg=
dependencies:
"@babel/runtime" "^7.10.2"
use-composed-ref "^1.0.0"
use-latest "^1.0.0"
react@^17.0.2:
version "17.0.2"
resolved "https://registry.nlark.com/react/download/react-17.0.2.tgz?cache=0&sync_timestamp=1623272232595&other_urls=https%3A%2F%2Fregistry.nlark.com%2Freact%2Fdownload%2Freact-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
@@ -9746,6 +9880,13 @@ react@^17.0.2:
loose-envify "^1.1.0"
object-assign "^4.1.1"
reactcss@^1.2.0:
version "1.2.3"
resolved "https://registry.npm.taobao.org/reactcss/download/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd"
integrity sha1-wAATh15Vexzw39mjaKHD2rO1SN0=
dependencies:
lodash "^4.0.1"
read-pkg-up@^3.0.0:
version "3.0.0"
resolved "https://registry.nlark.com/read-pkg-up/download/read-pkg-up-3.0.0.tgz?cache=0&sync_timestamp=1618846971516&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fread-pkg-up%2Fdownload%2Fread-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07"
@@ -10387,7 +10528,7 @@ set-value@^2.0.0, set-value@^2.0.1:
is-plain-object "^2.0.3"
split-string "^3.0.1"
setimmediate@^1.0.4:
setimmediate@^1.0.4, setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.nlark.com/setimmediate/download/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
@@ -11142,6 +11283,11 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.3:
resolved "https://registry.npm.taobao.org/tiny-warning/download/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha1-lKMNtFPfTGQ9D9VmBg1gqHXYR1Q=
tinycolor2@^1.4.1:
version "1.4.2"
resolved "https://registry.nlark.com/tinycolor2/download/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
integrity sha1-P2pNEHGtB2dtf6Ry4frECnGdiAM=
tmpl@1.0.x:
version "1.0.4"
resolved "https://registry.npm.taobao.org/tmpl/download/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
@@ -11220,6 +11366,11 @@ tryer@^1.0.1:
resolved "https://registry.npm.taobao.org/tryer/download/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
integrity sha1-8shUBoALmw90yfdGW4HqrSQSUvg=
ts-essentials@^2.0.3:
version "2.0.12"
resolved "https://registry.nlark.com/ts-essentials/download/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745"
integrity sha1-yTA/PXT3X6dSjD1JuA4ImrCdh0U=
ts-pnp@1.2.0, ts-pnp@^1.1.6:
version "1.2.0"
resolved "https://registry.npm.taobao.org/ts-pnp/download/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
@@ -11336,6 +11487,11 @@ typedarray@^0.0.6:
resolved "https://registry.nlark.com/typedarray/download/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
ua-parser-js@^0.7.18:
version "0.7.28"
resolved "https://registry.nlark.com/ua-parser-js/download/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
integrity sha1-i6BOZT81ziECOcZGYWhb+RId7DE=
unbox-primitive@^1.0.1:
version "1.0.1"
resolved "https://registry.npm.taobao.org/unbox-primitive/download/unbox-primitive-1.0.1.tgz?cache=0&sync_timestamp=1616706302651&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Funbox-primitive%2Fdownload%2Funbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
@@ -11480,6 +11636,25 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
use-composed-ref@^1.0.0:
version "1.1.0"
resolved "https://registry.npm.taobao.org/use-composed-ref/download/use-composed-ref-1.1.0.tgz#9220e4e94a97b7b02d7d27eaeab0b37034438bbc"
integrity sha1-kiDk6UqXt7AtfSfq6rCzcDRDi7w=
dependencies:
ts-essentials "^2.0.3"
use-isomorphic-layout-effect@^1.0.0:
version "1.1.1"
resolved "https://registry.npm.taobao.org/use-isomorphic-layout-effect/download/use-isomorphic-layout-effect-1.1.1.tgz#7bb6589170cd2987a152042f9084f9effb75c225"
integrity sha1-e7ZYkXDNKYehUgQvkIT57/t1wiU=
use-latest@^1.0.0:
version "1.2.0"
resolved "https://registry.npm.taobao.org/use-latest/download/use-latest-1.2.0.tgz#a44f6572b8288e0972ec411bdd0840ada366f232"
integrity sha1-pE9lcrgojgly7EEb3QhAraNm8jI=
dependencies:
use-isomorphic-layout-effect "^1.0.0"
use@^3.1.0:
version "3.1.1"
resolved "https://registry.nlark.com/use/download/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"