2023年2月

字段的权限控制,一般就是控制对应角色人员对某个业务对象的一些敏感字段的可访问性:包括可见、可编辑性等处理。本篇随笔结合基于SqlSugar的开发框架进行的字段控制管理介绍。

在设计字段权限的时候,我们需要了解这些还是基于RBAC的概念,基于角色进行授权的,而且我们的字段列表是属于具体的业务对象列表的,这里的业务对象是指一些我们具体的业务模块,如客户基础信息、人员基础信息、报价单等等,我们就是基于这些业务进行字段的控制的。

1、字段权限表的设计和界面管理

基于SqlSugar的开发框架,对应处理的业务对象信息的,是相关的实体对象信息,我们在数据库中设计两个数据库,一个用于存储对应实体类名称的信息,如ID,实体类全名,类路径等主体信息;一个存储对应角色配置的字段列表信息,结合起来就可以实现对应角色的字段权限控制了,数据库表设计信息如下所示。

两个业务表的部分字段信息如下所示。

系统中对于字段权限的设置界面如下所示。

通过列表对每个实体对象信息进行配置,如果没有对应的实体,从程序集中选择列出来即可加入进来。

加入对应的实体信息,我们可以根据名称反射接口获得对应字段参考的中文信息,然后进行展示,用于加入控制列表。

这样配置后,系统就记录了相关的配置信息,我们接下来就可以利用这些配置信息,在Winform界面上进行控件的显示处理操作了。

2、在Winform界面上进行控件的显示处理操作

在界面上,我们为了绑定界面控件和字段的关系,需要设置一个Tag来标识,然后根据系统配置信息,自动进行字段权限的控制处理(隐藏、设置只读,不限制等)

/// <summary>
///设置控件字段的权限显示或者隐藏(默认不使用字段权限)/// </summary>
private async voidSetPermit()
{
#region 设置控件和字段的对应关系 this.txtName.Tag = "Name";this.txtAge.Tag = "Age";#endregion //获取列表权限的列表 var permitDict = await BLLFactory<IFieldPermitService>.Instance.GetColumnsPermit(typeof(CustomerInfo).FullName, LoginUserInfo.Id.ToInt32());this.SetControlPermit(permitDict, this.layoutControl1);awaitTask.CompletedTask;
}

设置控件的字段显示方式 SetControlPermit ,代码如下所示。

        /// <summary>
        ///设置控件的可见、读写权限显示/// </summary>
        /// <param name="panel">控件对象</param>
        /// <param name="permitDict">字段和权限字典,字典值为权限控制:0可读写,1只读,2隐藏值,3不显示</param>
        /// <param name="layoutControl">如果存在布局,则使用布局控件,否则为空</param>
        public static void SetControlPermit(this Control panel, Dictionary<string, int> permitDict, LayoutControl layoutControl = null)
{
foreach (Control ctrl inpanel.Controls)
{
var baseCtrl = ctrl asBaseEdit;if (baseCtrl != null)
{
var tag = string.Concat(baseCtrl.Tag);if (!string.IsNullOrEmpty(tag) &&permitDict.ContainsKey(tag))
{
var permit =permitDict[tag];var visible = (permit == 0 || permit == 1);//2、3不可见 if (layoutControl != null)
{
var layoutItem =layoutControl.GetItemByControl(baseCtrl);if (layoutItem != null)
{
layoutItem.ToVisibility(visible);
}
}
baseCtrl.Visible
=visible;
baseCtrl.ReadOnly
= permit == 1;
}
}
ctrl.SetControlPermit(permitDict, layoutControl);
}
}

上面代码主要就是变量面板中的控件,并判断tag标签,然后进行只读、不可见、正常等的判断。

在列表界面中,我们可以通过设置隐藏字符让内容隐藏,如下界面效果所示。

如果不可见,就在界面上不显示这个列了,而非隐藏。

同样类似编辑控件界面,我们在列表界面也提供了对应的方法,用于隐藏列表字段的某些信息,如下代码所示。

//获取字段显示权限,并设置(默认不使用字段权限)
this.winGridViewPager1.gridView1.SetColumnsPermit(permitDict); 

它的实现规则也是类似,根据配置的字段权限控制点信息进行处理,决定是否显示,是否隐藏,是否正常处理。

它的permitDic也是根据配置信息读取出来进行判断即可。

//根据业务对象获取对应的显示字段,如果没有设置,那么根据FieldPermit表的配置获取字段权限列表
var permitDict = await BLLFactory<IFieldPermitService>.Instance.GetColumnsPermit(typeof(BlackIPInfo).FullName, Portal.gc.UserInfo.Id);var displayColumns = await BLLFactory<IBlackIPService>.Instance.GetDisplayColumns();if (permitDict != null && permitDict.Keys.Count > 0)
{
//0可读写,1只读,2隐藏值,3不显示 displayColumns = string.Join(",", permitDict.Keys.Where(s => permitDict[s] < 2));
}
this.winGridViewPager1.DisplayColumns = displayColumns;

下面是具体逻辑的通用控制方法,把它作为控件的扩展函数,我们就只需要一行代码调用就是写控制了

/// <summary>
///根据参数权限字典的值:0可读写,1只读,2隐藏值,3不显示,设置列的权限。/// </summary>
/// <param name="gridView">GridView对象</param>
/// <param name="fieNamePermitDict">字段和权限字典,字典值为权限控制:0可读写,1只读,2隐藏值,3不显示</param>
public static void SetColumnsPermit(this GridView gridView, Dictionary<string,int>fieNamePermitDict)
{
char passwordChar = '*';foreach (GridColumn col ingridView.Columns)
{
var include =fieNamePermitDict.ContainsKey(col.FieldName);if(include)
{
int permit =fieNamePermitDict[col.FieldName];switch(permit)
{
case 0://正常可见、可读写 col.OptionsColumn.AllowEdit = true;
col.OptionsColumn.ReadOnly
= false;
col.AppearanceHeader.ForeColor
=Color.Black;

col.Visible
= true;break;case 1://只读 col.OptionsColumn.AllowEdit = false;
col.OptionsColumn.ReadOnly
= true;
col.AppearanceHeader.ForeColor
=Color.Gray;

col.Visible
= true;break;case 2://隐藏值 var edit =col.CreateTextEdit();
col.Tag
= string.Concat(passwordChar);//用来在界面端进行判断,避免设置DisplayText edit.PasswordChar =passwordChar;
col.Visible
= true;break;case 3://不可见 col.Visible = false;break;
}
}
}
}

通过上面的代码处理,我们就能实现对Winform界面中的列表,编辑窗体的控件,进行相关的字段权限控制显示了。如下是实现的界面效果。

这样在系统后台,就可以根据需要设置一些敏感字段的信息隐藏或者只读处理了。

系列文章:


基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用


基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中间表的查询处理


基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发


基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理


基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转


基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口


基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传


基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录


基于SqlSugar的开发框架循序渐进介绍(9)-- 结合Winform控件实现字段的权限控制


基于SqlSugar的开发框架循序渐进介绍(10)-- 利用axios组件的封装,实现对后端API数据的访问和基类的统一封装处理


基于SqlSugar的开发框架循序渐进介绍(11)-- 使用TypeScript和Vue3的Setup语法糖编写页面和组件的总结


基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理


基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用


基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用


基于SqlSugar的开发框架循序渐进介绍(15)-- 整合代码生成工具进行前端界面的生成


基于SqlSugar的开发框架循序渐进介绍(16)-- 工作流模块的功能介绍


基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理


基于SqlSugar的开发框架循序渐进介绍(18)-- 基于代码生成工具Database2Sharp,快速生成Vue3+TypeScript的前端界面和Winform端界面

DevExpress提供了一个比较强大的图形绘制工具,可以用于绘制各种图形,如流程图、组织机构图等等,本篇随笔介绍XtraDiagram.DiagramControl的使用,以及利用代码对其属性进行控制,以及利用图形模具的自定义操作,实现一些简单流程图形的绘制和处理。

DiagramControl是类似Visio的绘图控件,以前我2006年的就接触使用Visio的二次开发,当时开始还是利用VB6 + VIsio2003进行二次开发的,后来把它改良为C# + Visio进行二次开发,DiagramControl的对象模型很类似Visio的相关对象模型,如对于工具栏的形状,称之为模具(Stencil),Visio也是称之为Stencil, DiagramControl里面的很多接口名称依旧采用Stencil进行命名,因此估计也是借鉴了很多Visio的对象设计知识,如果您对Visio二次开发感兴趣,可以参考我的随笔文章《
Visio二次开发
》,里面有很多相关的内容。

而如果想了解这个控件的相关知识和使用,参考官网的案例和说明应该是比较好的教程(
https://docs.devexpress.com/WindowsForms/118290/controls-and-libraries/diagrams/getting-started
)。

1、DiagramControl控件的使用

DiagramControl是一个界面控件,类似Visio SDK里面的DrawingControl的存在,可以通过它进行图形的绘制,各种窗口的显示和隐藏,以及跟踪各种事件的处理。

DiagramControl控件拖动到窗体中后,会自动增加一些属性窗口,上排的绘图工具中的按钮是我添加的,用来测试该控件的一些属性的控制。

1)属性窗口的显示和隐藏(折叠)

这个通过控制diagramControl1.OptionsView.PropertiesPanelVisibility 属性就可以实现对这个属性窗口的控制了。

里面显示一些系统位置和内容信息,以及一些自定义信息的窗口,后面我会介绍如何自定义处理这些模具的属性。

通过按钮处理的代码,我们可以实现对这个窗口的显示或者隐藏处理。

//切换属性窗口的显示或关闭
var status =diagramControl1.OptionsView.PropertiesPanelVisibility;
diagramControl1.OptionsView.PropertiesPanelVisibility
= (status == PropertiesPanelVisibility.Visible ? PropertiesPanelVisibility.Collapsed : PropertiesPanelVisibility.Visible);

2)模具形状窗口的显示或隐藏

模具形状的窗口,它是放在一个面板里面,我们只需要通过控制该面板的显示或者隐藏就可以了,如下代码所示。

//切换模具形状窗口的显示或关闭
var status =diagramToolboxDockPanel1.Visibility;
diagramToolboxDockPanel1.Visibility
= (status == DevExpress.XtraBars.Docking.DockVisibility.Visible ? DevExpress.XtraBars.Docking.DockVisibility.Hidden : DevExpress.XtraBars.Docking.DockVisibility.Visible);

或者通过控件的Toolbar属性进行控制,一样的效果。

//切换模具形状窗口的显示或关闭
var status = this.diagramControl1.OptionsView.ToolboxVisibility;this.diagramControl1.OptionsView.ToolboxVisibility = status == ToolboxVisibility.Closed ? ToolboxVisibility.Full : ToolboxVisibility.Closed;

3)放大缩小窗口的显示或者隐藏

同样我们也可以控制放大缩小窗口的显示或者隐藏,它也是图形绘制的一个常见的窗口。我们只需要判断或者设置diagramControl1.OptionsView.ShowPanAndZoomPanel 属性就可以了,如下代码所示。

//切换放大缩小窗口的显示或关闭
var status =diagramControl1.OptionsView.ShowPanAndZoomPanel;
diagramControl1.OptionsView.ShowPanAndZoomPanel
= !status;

4)其他属性的处理

另外,我们可以通过控制一些属性,实现对标尺、网格、只读视图等模式进行控制。

//是否显示标尺
this.diagramControl1.OptionsView.ShowRulers = this.chkRuler.Checked;//是否显示网格
this.diagramControl1.OptionsView.ShowGrid = this.chkGrid.Checked;//是否只读视图
this.diagramControl1.OptionsProtection.IsReadOnly = this.chkReadOnly.Checked;

2、绘图的处理事件

在绘制图形的时候,一般来说我们可能需要切换点选模式或者连接线模式,因此可以通过它的属性ActiveTool进行设置。在点选模式下,可以对图形进行拖动、放大缩小、旋转等处理,连接线模式下,则会加亮连接点,便于自动绘制连接线。

private void btnPointerMode_Click(objectsender, EventArgs e)
{
diagramControl1.OptionsBehavior.ActiveTool
=diagramControl1.OptionsBehavior.PointerTool;
}
private void btnConnectorMode_Click(objectsender, EventArgs e)
{
diagramControl1.OptionsBehavior.ActiveTool
=diagramControl1.OptionsBehavior.ConnectorTool;
}

当然,我们也可以通过对鼠标行为的分析来进行控制,如果鼠标悬停或者放置在图形上,就自动切换模式为连接线模式,否则为点选模式,那么只需要判断鼠标的移动行为即可自动处理,如下代码所示。

        /// <summary>
        ///实现对图形自动切换到连接点模式/// </summary>
        private void diagramControl1_MouseMove(objectsender, MouseEventArgs e)
{
if (e.Button ==MouseButtons.Left)return;

DiagramItem item
=diagramControl1.CalcHitItem(e.Location);if (item == null)
{
diagramControl1.OptionsBehavior.ActiveTool
=diagramControl1.OptionsBehavior.PointerTool;return;
}
else if (item isDiagramConnector)
{
diagramControl1.OptionsBehavior.ActiveTool
=diagramControl1.OptionsBehavior.ConnectorTool;return;
}

Rect itemBounds
= new Rect(new Point(item.Position.X, item.Position.Y), newSize(item.Width, item.Height));
PointFloat documentPoint
= diagramControl1.PointToDocument(newPointFloat(e.Location));
DiagramHitInfo[] hitInfo
=diagramControl1.CalcHitInfo(documentPoint);if (itemBounds.Contains(newPoint(documentPoint.X, documentPoint.Y)))
{
itemBounds.Inflate(
-5, -5);if (!itemBounds.Contains(newPoint(documentPoint.X, documentPoint.Y)))
{
diagramControl1.OptionsBehavior.ActiveTool
=diagramControl1.OptionsBehavior.ConnectorTool;return;
}
}
diagramControl1.OptionsBehavior.ActiveTool
=diagramControl1.OptionsBehavior.PointerTool;
}

另外图形的保存xml、PNG、PDF处理和加载代码如下所示。

/// <summary>
///保存XML和图片文件/// </summary>
private voidSaveXml()
{
var xml = Path.Combine(Application.StartupPath, "MyFlowShapes.xml");
diagramControl1.SaveDocument(xml);
var pngFile = Path.Combine(Application.StartupPath, "MyFlowShapes.png");
diagramControl1.ExportDiagram(pngFile);
}
private void btnLoadXml_Click(objectsender, EventArgs e)
{
var xml =FileDialogHelper.OpenXml();if(!string.IsNullOrEmpty(xml))
{
diagramControl1.LoadDocument(xml);
}
}

最终案例的效果如下所示。

3、注册自定义的形状

在实际的图形绘制开发中,我们可以需要创建一些指定的形状模具,那么我们弄好后一般可以存放在XML中,然后进行加载到控件上来,如下代码就是注册自定义的形状的处理。

/// <summary>
///注册自定义的形状。///自定义图形是以XML文件形式进行保存,图形需要按照规定XML格式进行绘制/// </summary>
private voidLoadShapes2()
{
var projectName = "SmallExampleDemo.Examples.XtraDiagram";using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(projectName + ".CustomContainers.xml"))
{
var stencil = DiagramStencil.Create(MyStencilId, MyStencilName, stream, shapeName =>shapeName);
DiagramToolboxRegistrator.RegisterStencil(stencil);
}
diagramControl1.SelectedStencils
= new StencilCollection(MyStencilId);//(MyStencilId, BasicShapes.StencilId); }

我们只需要设置选中的图形就可以了,其他有需要的可以从More Shapes中选择即可。

我们如果需要在属性窗口中显示自定义的属性,那么我们需要一些代码开发才能实现。

我们首先需要继承一个DiagramShape的子类,然后实现自己自定义的属性定义,如下代码所示。

对自定义属性的处理,需要在事件中实现

diagramControl1.CustomGetEditableItemProperties += DiagramControl_CustomGetEditableItemProperties;

通过对它进行判断可以实现自定义属性的显示处理

void DiagramControl_CustomGetEditableItemProperties(objectsender, DiagramCustomGetEditableItemPropertiesEventArgs e)
{
if (e.Item isDiagramShapeEx)
{
e.Properties.Add(TypeDescriptor.GetProperties(
typeof(DiagramShapeEx))["Status"]);
e.Properties.Add(TypeDescriptor.GetProperties(
typeof(DiagramShapeEx))["TypeName"]);
}
}

然后我们可以注册创建自己的模具形状集合,如下代码所示。

/// <summary>
///创建自定义的模具/// </summary>
/// <returns></returns>
DiagramStencil CreateCustomDrawShapesStencil()
{
var stencilId = "CustomedFlowShape";var stencilName = "流程图";var shapeSizeSmall = new Size(100, 37.5);var shapeSize = new Size(100, 75);

DiagramControl.ItemTypeRegistrator.Register(
typeof(DiagramShapeEx));var stencil = newDiagramStencil(stencilId, stencilName);//流程类型 stencil.RegisterTool(new FactoryItemTool("StartEnd", () => "流程开始", diagram =>{var shape = new DiagramShapeEx(BasicFlowchartShapes.StartEnd, "流程开始");
shape.Appearance.BackColor
=Color.Red;returnshape;
}, shapeSizeSmall));
stencil.RegisterTool(
new FactoryItemTool("Decision", () => "流程条件", diagram =>{var shape = new DiagramShapeEx(BasicFlowchartShapes.Decision, "流程条件");
shape.Appearance.BackColor
= Color.FromArgb(199, 115, 1);//Color.Red; returnshape;
}, shapeSize));

这两个流程开始,流程条件,我们直接是从 BasicFlowchartShapes 集合中借用过来,构建自己的自定义对象的,默认创建的对象是方形的。

如果我们需要动态构建其他自定义类型,我们可以指定它的颜色等样式,从而构建不同类型的图形。

//循环添加相关流程节点
var procNames = new List<string> { "审批", "归档", "阅办", "会签", "领导批示分阅"};//定义几个初始化颜色顺序
var colors = new List<Color>{ Color.DeepSkyBlue, Color.ForestGreen, Color.Violet, Color.Yellow, Color.Blue, Color.Orange, Color.Indigo, Color.Purple, Color.Black, Color.Brown, Color.Pink };int i = 0;foreach (string name inprocNames)
{
var shapeId = string.Format("Process_{0}", i++);

stencil.RegisterTool(
new FactoryItemTool(shapeId, () => name, diagram =>{var shape = newDiagramShapeEx(name, Status.Inactive);var index =procNames.IndexOf(name);var color = colors[index % 10];//Color.Red; var fontColor = (color == Color.Yellow) ?Color.Black : Color.White;//没什么作用//shape.ThemeStyleId = GetStyle(index);//从Accent1样式开始 DiagramShapeStyleId.Styles[index];// shape.Appearance.BackColor =color;
shape.Appearance.BorderSize
= 3;
shape.Appearance.Font
= new Font("宋体", 12f, FontStyle.Bold);
shape.Appearance.ForeColor
=fontColor;returnshape;
}, shapeSize));
}

这样就有不同颜色的图形对象了。

根据这些我们就可以绘制出自己的各种流程图了,并且也可以根据数据库的信息,进行动态绘制展示。

在SqlSugar的开发框架的后端,我们基于Web API的封装了统一的返回结果,使得WebAPI的接口返回值更加简洁,而在前端,我们也需要统一对返回的结果进行解析,并获取和Web API接口对应的数据进行展示即可,本篇随笔介绍在Vue3+TypeScript+Vite的项目中,使用基于TypeScript的基类继承的方式,实现对后端接口数据的统一解析处理的封装操作。

1、SqlSugar的开发框架后端Web API的封装

前面介绍到,在SqlSugar的开发框架的后端,我们需要对Web API统一封装返回结果,如对于授权登录的接口,我们的接口定义如下所示。

        /// <summary>
        ///登录授权处理/// </summary>
        /// <returns></returns>
[AllowAnonymous]
[HttpPost]
[Route(
"authenticate")]public async Task<AuthenticateResultDto> Authenticate(LoginDto dto)

其中的Web API的返回结果定义如下所示。

    /// <summary>
    ///授权结果对象/// </summary>
    public classAuthenticateResultDto
{
/// <summary> ///令牌信息/// </summary> public string? AccessToken { get; set; }/// <summary> ///失效秒数/// </summary> public int Expires { get; set; }/// <summary> ///处理是否成功/// </summary> public bool Succes { get; set; }/// <summary> ///错误信息/// </summary> public string? Error { get; set; }
}

我们注意到  Authenticate 的Web API方法返回的结果是一些简单的业务信息,一般我们返回结果需要在进行统一的封装处理,以便达到统一的外部处理需要。

关于接口数据格式的统一封装,我们定义一个WrapResultFilter,以及需要一个不封装的属性标识DontWrapResultAttribute,默认是统一封装返回的结果。

而对于结果的统一封装,我们只需要在Web API服务启动的时候,加入相关的过滤器处理即可。

//控制器添加自定义过滤器
builder.Services.AddControllers(options=>{
options.Filters.Add
<WrapResultFilter>(); //统一结果封装处理 options.Filters.Add<GlobalExceptionFilter>();//自定义异常处理 });//所有控制器启动身份验证 builder.Services.AddMvc(options =>{
options.Filters.Add(
new AuthorizeFilter());//所有MVC服务默认添加授权标签 });

如下是Web API统一封装后返回的结果对象。

而对于列表记录的返回,同样是进行了外围的封装。

        /// <summary>
        ///获取所有记录/// </summary>
[HttpGet]
[Route(
"all")]
[HttpGet]
public virtual async Task<ListResultDto<TEntity>> GetAllAsync()

2、利用axios组件对后端API数据的访问和基类的统一封装处理

利用axios组件对后端Web API的调用,基本上是前端开发的标准做法了。

一般来说,我们为了方便,会对原生的axios组件进行一定的封装处理,以便支持更好的调用处理,一般场景的操作就是POST、GET、PUT、DELETE,以及对文件流的上传处理操作,axios的github地址是
https://github.com/axios/axios
,如果需要可以参考它来做封装处理即可。

本篇随笔基于
https://github.com/vbenjs/vue-vben-admin
项目上的axios组件封装进行使用,它对于axios组件的封装比项目
https://github.com/xiaoxian521/vue-pure-admin
上的封装更加丰富一些,因此采用它。

利用axios组件,一般是为了方便,采用Typescript对它进行一定的封装,并利于统一对Request和Response的对象统一拦截处理,如Request请求接口调用的时候,根据当前用户的token进行头部信息的注入,获取到接口后,可以对结果内容进行拆解,获得简化后的结果。

例如对于常规的POST、GET、PUT、DELETE的处理,统一进行了调用,根据配置参数进行处理

  get<T = any>(
config: AxiosRequestConfig,
options
?: RequestOptions
): Promise
<T>{return this.request({ ...config, method: "GET"}, options);
}

post
<T = any>(
config: AxiosRequestConfig,
options
?: RequestOptions
): Promise
<T>{return this.request({ ...config, method: "POST"}, options);
}

put
<T = any>(
config: AxiosRequestConfig,
options
?: RequestOptions
): Promise
<T>{return this.request({ ...config, method: "PUT"}, options);
}
delete<T = any>(
config: AxiosRequestConfig,
options
?: RequestOptions
): Promise
<T>{return this.request({ ...config, method: "DELETE"}, options);
}

如对于HTTP请求拦截,我们需要在配置信息中加入token令牌信息,如下代码所示。

  /**
* @description: 请求拦截器处理
*/requestInterceptors: (config, options)=>{//请求之前处理config const tokenString =getToken();//console.log(tokenString); if(tokenString) {
const data
=JSON.parse(tokenString) as AuthenticateDto;
const now
= newDate().getTime();
const expired
= parseInt(data.expires) - now <= 0;//console.log(data, now, expired); if(expired) {//token过期刷新 }

const token
=data.accessToken;if(
token
&&(config as Recordable)?.requestOptions?.withToken !== false) {//jwt token (config as Recordable).headers.Authorization =options.authenticationScheme?`${options.authenticationScheme} ${token}`
: token;
}
}
returnconfig;
},

这些我们进行一定的微调即可,大多数情况下,不需要进行太多的设置。

对于统一返回的结果,我们为了方便,统一进行了处理。在前端定义好几个数据类型,最后返回结果result即可。

在以前写过的关于前端Web API的处理文章中,有《
循序渐进VUE+Element 前端应用开发(19)--- 后端查询接口和Vue前端的整合
》 、《
在Bootstrap开发框架基础上增加WebApi+Vue&Element的前端
》,都是对相关业务类进行接口的抽象封装,以便于重用服务器的逻辑调用。

Vue&Element的前端的架构设计如下所示。

一般来说,我们页面模块可能会涉及到Store模块,用来存储对应的状态信息,也可能是直接访问API模块,实现数据的调用并展示。在页面开发过程中,多数情况下,不需要Store模块进行交互,一般只需要存储对应页面数据为全局数据状态的情况下,才可能启用Store模块的处理。

通过WebProxy代理的处理,我们可以很容易在前端中实现跨域的处理,不同的路径调用不同的域名地址API都可以,最终转换为本地的API调用,这就是跨域的处理操作。

3、访问后端接口的ES6 基类的封装处理

前端根据框架后端的接口进行前端JS端的类的封装处理,引入了ES6类的概念实现业务基类接口的统一封装,简化代码。

权限模块我们涉及到的用户管理、机构管理、角色管理、菜单管理、功能管理、操作日志、登录日志等业务类,那么这些类继承BaseApi,就会具有相关的接口了,如下所示继承关系。

按照这个思路,我们在BaseApi的ES6类里面定义了对应Web API基类里面的操作方法,如下所示。

这样,我们在创建一个业务类的时候,如果没有特殊的自定义接口,只需要继承基类BaseApi即可具有所有的常规基类方法了。

//导入API基类对象,默认具有Get/GetAll/Create/Update/Delete/BatchDelete/SaveImport/Count等接口
import BaseApi from "./base-api";//业务类自定义接口实现, 通用的接口已经在BaseApi中定义
class Api extends BaseApi {//参考下面案例,增加自定义函数
  //GET 方法例子
  //根据条件计算记录数量
  //async GetCount(params: object) {
  //return await this.HttpGet<number>(this.baseurl + "count", params);
  //}
  //POST 方法例子
  //创建对象
  //async Create(data: object) {
  //return await this.HttpPost<boolean>(this.baseurl + `create`, data);
  //}
  //PUT 方法例子
  //更新对象
  //async Update(data: object) {
  //return await this.HttpPut<boolean>(this.baseurl + `update`, data);
  //}
  //DELETE 方法例子
  //删除指定ID的对象
  //async Delete(id: number | string) {
  //return await this.HttpDelete<boolean>(this.baseurl + `${id}`);
  //}
}//构造客户信息 Api实例,并传递业务类接口地址
export default new Api("/api/customer/");

如果需要一些定制的方法,我们则根据注释的提示和Web API的路径声明进行编写即可,如下是一个自定义接口的处理。

  //根据字典类型获取对应的TreeNodeItem集合(包括id, label属性)
async GetTreeItemByDictType(dictTypeName: string) {return await this.HttpGet<TreeNodeItem[]>(this.baseurl + `treeitem-by-typename/${dictTypeName}`
);
}

由于是基于TypeScript,我们在具体的位置中定义了TreeNodeItem类型,对应服务器返回的WebAPI类型即可。

//树节点类型
export interface TreeNodeItem {
id: string;
label: string;
key
?: string;
children
?: TreeNodeItem[];
}

然后在自定义的ES6类的顶部引入类型定义就可以了

import {
PagedResult,
ListResult,
TreeNodeItem,
CListItem,
CommonResult
} from
"./types";

我们定义了接口后,就可以在Vue的JS里面进行调用了。

//使用字典类型,从服务器请求数据
await dictdata.GetTreeItemByDictType(typeName).then(list =>{
  if(list) {
list.forEach(item
=>{ dictItems.value.push({ id: item.id, label: item.label });
});
}
});

我们也可以使用async/await的异步线程调用方法,如下所示。

另外,由于我们的ES6接口定义,是基于TypeScript的,它的数据类型可以推断出来,因此在编码或者查看对应属性的时候,会有非常好的提示信息,如上所示。

最后我们来验证下实际的axios调用页面的效果。

以及另外一个复杂一点的测试页面展示。

后续继续介绍Vue3+TypeScript+ElementPlus的相关技术点。

系列文章:


基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用


基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中间表的查询处理


基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发


基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理


基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转


基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口


基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传


基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录


基于SqlSugar的开发框架循序渐进介绍(9)-- 结合Winform控件实现字段的权限控制


基于SqlSugar的开发框架循序渐进介绍(10)-- 利用axios组件的封装,实现对后端API数据的访问和基类的统一封装处理


基于SqlSugar的开发框架循序渐进介绍(11)-- 使用TypeScript和Vue3的Setup语法糖编写页面和组件的总结


基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理


基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用


基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用


基于SqlSugar的开发框架循序渐进介绍(15)-- 整合代码生成工具进行前端界面的生成


基于SqlSugar的开发框架循序渐进介绍(16)-- 工作流模块的功能介绍


基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理


基于SqlSugar的开发框架循序渐进介绍(18)-- 基于代码生成工具Database2Sharp,快速生成Vue3+TypeScript的前端界面和Winform端界面

随着Vue3和TypeScript的大浪潮不断袭来,越来越多的Vue项目采用了TypeScript的语法来编写代码,而Vue3的JS中的Setup语法糖也越来越广泛的使用,给我们这些以前用弱类型的JS语法编写Vue代码的人不少冲击,不过随着大量的学习和代码编写,经历过一段难熬的时间后,逐步适应了这种和之前差别不小的写法和冲击。本篇随笔介绍总结了Vue3中一些常见的基于TypeScript的Setup语法与组合式 API的处理代码案例。

TypeScript(简称ts)是微软推出的静态类型的语言,相比于js,TypeScript拥有强类型、编译器严谨的语法检查、更加严苛的语法,TypeScript 是 JS类型的超集,并支持了泛型、类型、命名空间、枚举等特性,弥补了 JS 在大型应用开发中的不足。TypeScript 是 JavaScript 的强类型版本,最终在浏览器中运行的仍然是 JavaScript,所以 TypeScript 并不依赖于浏览器的支持,也并不会带来兼容性问题。

基于TypeScript的Setup语法糖写法越来越多,熟练使用的话,需要一个学习过程,另外ElementPlus控件也有了一些不同的变化,而且它的官方案例代码基本上采用了Setup语法糖的写法来提供例子代码。

<script setup lang="ts">  是在单文件组件 (SFC) 中使用
组合式 API
的编译时语法糖。
script-setup
弱化了vue模板式编程体验,也使得代码更简洁。

1、定义组件或者页面名称

由于组合式API的特殊性,组件里面的各项内容可以分开进行定义,同时借助一些辅助函数进行处理。如这里定义组件或者页面名称,通过使用defineOptions进行声明。

<scriptsetup lang="ts">import { reactive,  ref,  onMounted,  watch,  computed } from"vue";

defineOptions({ name:
"MyDictdata"});//定义组件或页面名称

如果是组件,通过这样定义后,我们在页面引入它的时候,就可以import这个名称就可以了,如下代码所示。

//自定义字典控件
import MyDictdata from "./src/my-dictdata.vue";

这样我们在页面中就可以和其他HTML标签一样使用这个组件了。

<my-dictdatav-model="editForm.nationality"type-name="民族" />

2、data属性定义

不管是Vue 页面还是组件,我们都需要设置一些属性信息,并提供一些初始化值,以前这些在选项式代码中的时候,是在data块中定义的,采用了<script setup lang="ts">语法后,任何在里面定义的信息,在当前页面或者组件的模板里面都是公开,可以访问的。

我们可以使用ref或者 reactive 来定义不同类型的,ref针对的是简单类型,reactive 针对的是对象类型,它们底层的实现是一样的,ref的参数增加了一个value的属性。

let expandMore = ref(false); //是否展开更多条件
let list = ref([]); //页面列表数据
let listSelection = ref([]); //选中记录
let loading = ref(true); //加载状态
let sorting = ref(""); //排序条件

//分页条件
let pageInfo =reactive({
pageIndex:
1,
pageSize:
20,
totalCount:
0});

这些信息可以在HTML页面中直接引用使用即可。

<!--分页部分-->
<divclass="block"style="height: 70px">
<el-paginationbackground :current-page="pageInfo.pageIndex":page-size="pageInfo.pageSize":total="pageInfo.totalCount":page-sizes="[10, 20, 30, 40]"layout="total, sizes, prev, pager, next,jumper"@size-change="sizeChange"@current-change="currentChange" />
</div>

不过记得,如果是在JS里面引用对象,那么记得加上.value的属性,才能设置或者访问它。

3、表单或者组件的ref引用

有时候,需要通过在页面的ref=“form” 来引用一些表单或者组件的名称,那么就需要初始化相关的类型的,如下代码所示。

const searchRef = ref<FormInstance>(); //表单引用

而这个需要引入对应的类型的。

import { FormInstance, FormRules } from "element-plus";

这样我们在HTML模板中就可以使用它的名称了。

而对于自定义组件的话,如果需要严谨类型的处理,一般也需要约束对应的类型,我们如果需要反射某个特定组件的类型,那么也可以使用
InstanceType
的关键字来处理,如下代码所示。

<scriptlang="ts"setup>import { ref } from'vue'import { ElTree } from'element-plus'const treeRef=ref<InstanceType<typeofElTree>>()

这样在调用相关接口方法的时候,就有Typescript的只能提示,代码更加健壮了。

通过
InstanceType
这样方式获得的ref引用,会显示组件很多公开的属性和接口方法,如下图所示。

我们也可以单独定义一个类型,用来约束自定义组件的方法或者属性,如下我们定义一个视图类型组件,只有一个show方法。

我们在<script setup lang="ts">的顶部export一个接口定义,然后再在下面使用
defineExpose
暴露组件属性和方法,这样就可以在组件的引用的地方调用这些方法了。

<scriptsetup lang="ts">
//组件的接口类型
export interface ExposeViewType{
show(id
?: string|number): Function;
}
//显示窗口 const show=(id: string|number)=>{if(!isNullOrUnDef(id)) {
testuser.Get(id).then(data
=>{
Object.assign(viewForm, data);

isVisible.value
= true;//显示对话框 });
}
};
//暴露组件属性和方法 defineExpose({
show
});

这样我们在页面中定义这个自定义组件的引用的时候,除了使用InstanceType之外,还可以使用自定义的类型声明了。

    <!--查看详细组件界面-->
    <view-dataref="viewRef" />

在<script setup lang="ts">里面定义对应引用的类型。

const viewRef = ref<ExposeViewType| null>(); //查看表单引用

这样我们就可以在代码中查看它的对外公布的方法信息了。

4、组件prop属性定义

在我们开发自定义组件的时候,我们往往需要定义很多父传子的属性,也叫作prop属性定义。

prop属性定义,是通过defineProps函数进行处理的,这个
defineProps()
宏函数支持从它的参数中
推导类型
,定义的代码如下所示。

<scriptsetup lang="ts">const props=defineProps<{
foo: string
bar
?: number
}
>()</script>

我们也可以将 prop 的类型移入一个单独的
接口
中:

<scriptsetup lang="ts">interface Props {
foo: string
bar
?: number
}

const props
=defineProps<Props>()</script>

有时候,我们还需要给指定的prop属性给定默认值,那么也可以通过函数withDefaults一起进行处理即可。

如下面是我们指定模块定义的prop接口信息和defineProps的处理代码。

<scriptsetup lang="ts">import {  reactive,  ref,  onMounted,  watch,  computed} from 
vue
";

//定义组件名称
defineOptions({ name:
"MyDictdata"});

//声明Props的接口类型
interface Props {
placeholder?: string; // 空白提示
typeName?: string; // 字典类型方式,从后端字典接口获取数据
options?: Array<TreeNodeItem>; // 固定列表方式,直接绑定,项目包括id,label属性
modelvalue?: string | number; // 接受外部v-model传入的值
clearable?: boolean; // 是否可以清空
disabled?: boolean; // 是否禁用
multiple?: boolean; // 是否多选
}

//使用默认值定义Props
const props = withDefaults(defineProps<Props>(), {
placeholder:
"请选择",
typeName:
"",
options: () => {
return [];
},
clearable: true,
disabled: false,
multiple: false,

modelValue:
""//对应自定义控件的v-model的值
});

这样我们在使用的时候,就可以传入给组件对应的prop名称了。

  <el-form-itemlabel="民族"prop="nationality">
    <my-dictdatav-model="editForm.nationality"type-name="民族" />
  </el-form-item>

5、Emits事件声明

在组件里面,我们抛出事件,通过在Emits中进行声明,再行使用。

声明事件在setup语法里面也是和其他宏函数一样,如下代码所示。

  //声明事件
  const emit = defineEmits(['updateName'])

如果为了更强的指定事件的参数和返回值等信息,我们也可以通过定义接口然后在声明Emits的方式,如下代码所示。

//声明控件事件
interface Emits {
(e:
"update:modelValue", value: string): void;
(e:
"change", value: string): void;
}
//定义控件事件 const emit = defineEmits<Emits>();

或者直接整合一起声明。

//基于类型的声明
const emit = defineEmits<{
(e:
'change', id: number): void(e:'update', value: string): void}>()

然后在组件的函数中触发事件,通知父页面即可。

function change(data) {
const obj = dictItems.value.find(item => {
return item.id + "" === data;
});
emit("change", obj);
}

这样我们在页面使用组件的时候,HTML模板中使用的组件代码里面,可以获得获得对应的事件处理。

  <el-form-itemlabel="状态"prop="state">
    <my-dictdatav-model="searchForm.state":options="Status"@change="change" />
  </el-form-item>

6、
Computed计算函数的使用

「computed」 是Vue中提供的一个计算属性。它被混入到Vue实例中,所有的getter和setter的this上下文自动的绑定为Vue实例。

<scriptsetup lang="ts">import { computed, ref } from'vue'const count=ref(1)//通过computed获得doubleCount
const doubleCount=computed(()=>{returncount.value* 2})//获取
console.log(doubleCount.value)</script>

7、Watch函数的使用

有时候,子组件需要监控自身某个值的变化,然后进行相关的处理,那么对值进行监控就需要用到了watch函数。

//监听外部对props属性变更,如通过ResetFields()方法重置值的时候
watch(
()
=>props.modelValue,
newValue
=>{
console.log(newValue);
emit(
"update:modelValue", newValue + "");
}
);

watch(
()
=>props.options,
newValue
=>{
newValue.forEach(item
=>{
dictItems.value.push(item);
});
}
);

8、onMounted函数的使用

我们一般在 onMounted 的逻辑里面准备好组件或者页面显示的内容,这里面在页面组件准备妥当后进行更新显示。

//页面初始化加载
onMounted(() =>{
getlist();
});

或者组件里面

//挂载的时候初始化数据
onMounted(async () =>{var typeName =props.typeName;var options =props.options;if (typeName && typeName !== "") {//使用字典类型,从服务器请求数据
    await dictdata.GetTreeItemByDictType(typeName).then(list =>{
      if(list) {
list.forEach(item
=>{ dictItems.value.push({ id: item.id, label: item.label });
});
}
});
}
else if (options && options.length > 0) {//使用固定字典列表 options.map(item =>{
dictItems.value.push({ id: item.id, label: item.label });
});
}
//设置默认值 keyword.value =props.modelValue;
});

9、自定义组件的ModelValue

一般组件在绑定值的时候,一般使用v-Model的属性来设置它的值。

  <el-form-itemlabel="姓名"prop="name">
    <el-inputv-model="editForm.name" />
  </el-form-item>

或者日期组件

  <el-form-itemlabel="出生日期"prop="birthDate">
    <el-date-pickerv-model="editForm.birthDate"align="right"type="date"placeholder="选择日期"format="YYYY-MM-DD" />
  </el-form-item>

因此我们自定义开发的组件,也应该采用这样约定的属性。这里面的v-Model对应的prop属性就是modelValue的,因此我们需要定义这个属性,并处理Emits事件就可以了。

//声明Props的接口类型
interface Props {
modelvalue
?: string | number; //接受外部v-model传入的值 }//使用默认值定义Props const props = withDefaults(defineProps<Props>(), {
modelValue:
"" //对应自定义控件的v-model的值 });

然后声明组件的事件,在组件内部合适的地方触发即可。

//声明组件事件
interface Emits {
(e:
"update:modelValue", value: string): void;
(e:
"change", value: string): void;
}
//定义组件事件 const emit = defineEmits<Emits>();

并在Watch监控它的变化,触发组件的自定义事件

watch(
()
=>props.modelValue,
newValue
=>{
console.log(newValue);
emit(
"update:modelValue", newValue + "");
}
);

10、自定义引入Vue的API和组件

上面所有的setup语法糖代码里面,我们在开始的时候,往往都需要引入ref,reactive等API,如下代码所示。

<scriptsetup lang="ts">import { reactive,  ref,  onMounted,  watch,  computed } from"vue";

那么每次引入局的麻烦的话,可以通过使用
https://github.com/antfu/unplugin-auto-import
这个插件来实现自动引入这些配置信息,这样每次就可以省却一些定义代码了。

这样在使用ref,reactive的时候,不用引入就直接使用,如下代码所示。

const count = ref(0)
const doubled
= computed(() => count.value * 2)

安装组件,直接通过下面npm 或者pnmp进行安装即可。

npm i -D unplugin-auto-import

它提供了Vite、WebPack等编译器的集成,可以参考官网进行修改。

如Vite的配置处理如下所示。

//vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'exportdefaultdefineConfig({
plugins: [
AutoImport({
/*options*/}),
],
})

然后对Typescript和ESLint进行修改配置一下就可以一劳永逸了(具体参考官网的说明),希望下个版本的vue能自动不用引入这些API就好了。

以上就是我们在<script setup lang="ts">语法中经常涉及到的一些常用的知识和代码案例了。

系列文章:


基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用


基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中间表的查询处理


基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发


基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理


基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转


基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口


基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传


基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录


基于SqlSugar的开发框架循序渐进介绍(9)-- 结合Winform控件实现字段的权限控制


基于SqlSugar的开发框架循序渐进介绍(10)-- 利用axios组件的封装,实现对后端API数据的访问和基类的统一封装处理


基于SqlSugar的开发框架循序渐进介绍(11)-- 使用TypeScript和Vue3的Setup语法糖编写页面和组件的总结


基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理


基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用


基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用


基于SqlSugar的开发框架循序渐进介绍(15)-- 整合代码生成工具进行前端界面的生成


基于SqlSugar的开发框架循序渐进介绍(16)-- 工作流模块的功能介绍


基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理


基于SqlSugar的开发框架循序渐进介绍(18)-- 基于代码生成工具Database2Sharp,快速生成Vue3+TypeScript的前端界面和Winform端界面

在早期的随笔就介绍过,把常规页面的内容拆分为几个不同的组件,如普通的页面,包括列表查询、详细资料查看、新增资料、编辑资料、导入资料等页面场景,这些内容相对比较独立,而有一定的代码量,本篇随笔介绍基于Vue3+Typescript+Setup语法方式,来拆分页面模块内容为组件,实现分而治之的处理。

1、页面模块组件的划分

我们先来了解下常规页面的内容的整体界面布局,它包含常规的列表界面,新增、编辑、查看、导入等界面,除了列表页面,其他内容以弹出层对话框的方式进行处理,如下界面示意图所示。

这些页面也可以放在一个大页面里面进行处理,逻辑代码也可以整合一起进行管理,大致的页面布局如下所示。

我们看到,如果这样放置页面的模块内容,如果界面控件比较多的话,页面代码会急剧增加,而且由于代码太多,管理起来也非常不方便,最好的方式,还是拆分进行组件化的管理比较好 。

我们以一个测试用户的页面为例来介绍,测试用户列表界面如下所示。

其中也包括了查看、编辑、新增、导入等界面,我们后面逐一介绍。

2、页面组件的开发

我们前面介绍到,整个页面包含了列表界面,新增、编辑、查看、导入等界面,除了列表页面,其他内容以弹出层对话框的方式进行处理。

我们分别创建index.vue代表主列表页面内容,view代表查看页面、edit代表新增或者编辑页面(两个页面类似,因此整合一起更精简),import代表导入页面,一起放在一个testuser页面目录中,作为一个模块页面。

我们先以view.vue查看页面为例进行介绍,它是一个查看明细的界面,因此也是一个弹出对话框页面,我们把它的代码处理如下所示。

<template>
  <el-dialogtitle="查看信息"v-model="isVisible"v-if="isVisible"append-to-body @close="closeDialog(viewRef)">
    <el-formref="viewRef":model="viewForm"label-width="80px">
      <el-tabstype="border-card">
        <el-tab-panelabel="基本信息">
          <el-row>
            <el-col:span="12">
              <el-form-itemlabel="姓名">
                <el-inputv-model="viewForm.name"disabled/>
              </el-form-item>
            </el-col>
            <el-col:span="12">
              <el-form-itemlabel="性别">
                <el-inputv-model="viewForm.sex"disabled/>
              </el-form-item>
            </el-col>.................//省略代码</el-tab-pane>
      </el-tabs>
    </el-form>
    <template#footer>
      <spanclass="dialog-footer">
        <el-button@click="closeDialog(viewRef)">关闭</el-button>
      </span>
    </template>
  </el-dialog>
</template>

其他的js代码采用tyepscript语法,我们把它放在

<scriptsetup lang="ts">
//逻辑代码
</script>

为了把组件的方法公开,我们先定义一个接口类型,便于引用的时候,代码进行约束提示。

<scriptsetup lang="ts">
//组件的接口类型
export interface ExposeViewType {
show(id
?: string|number): Function;
}

............
//显示窗口 const show=(id: string|number)=>{//处理代码 };//暴露组件属性和方法 defineExpose({
show
});
</script>

这样我们在父页面中使用子模块组件的时候,就可以通过公开的方法进行调用了。

//父页面index.vue<!--查看详细组件界面-->
    <view-dataref="viewRef" />
    <!--新增、编辑组件界面-->
    <edit-dataref="editRef"@submit="saveEdit" />
    <!--模板导入信息-->
    <import-dataref="importRef"@finish="finishImport" />
  </div>
</template>

<scriptsetup lang="ts">........

import ViewData, { ExposeViewType } from
"./view.vue";
import EditData from
"./edit.vue";
import ImportData from
"./import.vue";

......
//显示查看对话框处理 const viewRef=ref<ExposeViewType| null>();//查看表单引用//const viewRef = ref<InstanceType<typeof ViewData>>(); functionshowView(id) {if(isEmpty(id)) {
warnMessage(
"请选择编辑的记录!");return;
}
viewRef.value.show(id);
}

我们通过const viewRef = ref<ExposeViewType | null>();  就可以获得组件类型的引用,然后调用组件的接口方法即可。

viewRef.value.show(id);

在查看页面的组件定义模板中,我们大致代码如下所示。

声明了对应的引用,以及表单对象,以及提供相应的方法进行处理,这些内容对父页面封装了细节。

<scriptsetup lang="ts">
//组件的接口类型
export interface ExposeViewType {
show(id
?: string|number): Function;
}

import { reactive, ref, onMounted, watch, computed, nextTick } from
"vue";
import { FormInstance} from
"element-plus";

defineOptions({ name:
"ViewData"});//定义组件名称 //声明Props的接口类型 interface Props {
visible
?:boolean;//是否显示 id?: string|number;//接受外部v-model传入的id值 }//使用默认值定义Props const props=withDefaults(defineProps<Props>(), {
visible:
false,
value:
null});//声明组件事件 interface Emits{
(e:
"update:id", id: string|number):void;
(e:
"update:visible", visible:boolean):void;
(e:
"close"):void;//(e: "submit"): void; }//定义组件事件 const emit=defineEmits<Emits>();

我们定义了组件名称、组件的Props属性、以及Emit事件,Emit事件如果想简单化一点,也可以直接使用名称即可。

例如,有时候我们会直接声明名称进行定义Emit,如下所示。

//定义触发事件
const emit = defineEmits(["error", "success", "remove", "change"]);

显示页面的方法,是公开给父页面进行调用的,因此接收一个id参数,并根据id值,利用axios访问远端API接口获取数据,进行赋值显示即可。

//显示窗口
const show = (id: string | number) =>{if (!isNullOrUnDef(id)) {
testuser.Get(id).then(data
=>{//console.log(data); Object.assign(viewForm, data);

isVisible.value
= true; //显示对话框 });
}
};

关于axios访问远端API接口的类实现,可以参考随笔《
基于SqlSugar的开发框架循序渐进介绍(10)-- 利用axios组件的封装,实现对后端API数据的访问和基类的统一封装处理
》进行了解。

这里的TestUser的APi类,继承自基类BaseApi,因此拥有常规的处理方法。

最后,查看明细的窗口关闭后,需要设置一下窗口的相关标记。

let isVisible = ref(false); //是否显示查看对话框
function closeDialog(formEl: FormInstance |undefined) {//关闭常规 添加、编辑、查看、导入等窗口处理
  isVisible.value = false;if (!formEl) {
formEl.resetFields();
}
emit(
"close"); //关闭 }

由于窗口内部的显示标记和Prop属性的关系,我们需要处理一下,对他们进行Watch监控,并处理值的变化。

//监控某些值的变化,进行处理
watch(
()
=>props.visible,
newValue
=>{
isVisible.value
=newValue;
emit(
"update:visible", newValue);
}
);
watch(
()
=>isVisible,
newValue
=>{//console.log(newValue); emit("update:visible", newValue.value);
}
);

表单的form对象,我们根据后端数据结构进行生成即可。

const viewRef = ref<FormInstance>(); //表单引用//表单属性定义
let viewForm =reactive({
id:
"",
name:
"",
sex:
"",
birthDate:
"",
nationality:
"",
education:
"",
marriage:
"",
star:
"",
height:
"",
weight:
"",

.................

createTime:
"",
extensionData:
"" //扩展数据 });

有了这些处理,我们查看详细的页面弹出和关闭就正常了。页面效果如下所示。

新建、编辑页面也是类似,只是在保存数据后触发相关的事件,让父页面进行更新显示即可。

    <!--查看详细组件界面-->
    <view-dataref="viewRef" />
    <!--新增、编辑组件界面-->
    <edit-dataref="editRef"@submit="saveEdit" />
    <!--模板导入信息-->
    <import-dataref="importRef"@finish="finishImport" />

如编辑、新增页面的父组件页面,也是只需关注他的打开和完成处理即可。

//新增、编辑表单引用
const editRef = ref<ExposeViewType | null>();//显示新增对话框
functionshowAdd() {
editRef.value.show();
}
//显示编辑对话框 functionshowEdit(id) {if(isEmpty(id)) {
warnMessage(
"请选择编辑的记录!");return;
}
editRef.value.show(id);
}
//新增/更新后刷新 functionsaveEdit() {
getlist();
}

而在编辑信息的组件页面内部,就需要判断是更新还是插入记录的处理,完成后再抛出事件即可。

//保存数据处理
async functionsubmitData() {var formEl =editRef.value;if (!formEl) return;//console.log(editForm);
  await formEl.validate(async valid =>{if(valid) {//验证成功,执行下面方法
      var result = false;if(isAdd.value) {
result
= await testuser.Create(editForm); //新增保存 } else{
result
= await testuser.Update(editForm); //编辑保存 }if(result) {
successMessage(
"操作成功!"); //提示信息 emit("submit"); //提示刷新数据 closeDialog(formEl); //重置窗口状态 } else{
errorMessage(
"操作失败");
}
}
})

导入数据页面,大体也是类似,不过由于涉及到更多的是对导入处理的规则处理,需要封装一下相关的组件功能,因此后面再独立介绍细节实现。

系列文章:


基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用


基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中间表的查询处理


基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发


基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理


基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转


基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口


基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传


基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录


基于SqlSugar的开发框架循序渐进介绍(9)-- 结合Winform控件实现字段的权限控制


基于SqlSugar的开发框架循序渐进介绍(10)-- 利用axios组件的封装,实现对后端API数据的访问和基类的统一封装处理


基于SqlSugar的开发框架循序渐进介绍(11)-- 使用TypeScript和Vue3的Setup语法糖编写页面和组件的总结


基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理


基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用


基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用


基于SqlSugar的开发框架循序渐进介绍(15)-- 整合代码生成工具进行前端界面的生成


基于SqlSugar的开发框架循序渐进介绍(16)-- 工作流模块的功能介绍


基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理


基于SqlSugar的开发框架循序渐进介绍(18)-- 基于代码生成工具Database2Sharp,快速生成Vue3+TypeScript的前端界面和Winform端界面