2023年2月

由于一些客户的内部系统需要提取一些记录信息,如果手工录入会变得比较麻烦,因此考虑使用百度云的OCR进行图片文字的提取处理,综合比较了一下开源免费的Tesseract 类库进行处理,不过识别效果不太理想,因此转为了百度的OCR云接口处理方式,测试的效果比较理想,基本上较少出现错别字。本篇随笔介绍如何利用百度OCR进行图片文字的提取处理,以便从别的系统中批量化获得响应的系统数据,然后进行相应的格式化处理。

1、百度OCR服务申请

百度的OCR接口调用是收费的接口,不过一般是在超过一定量的情况后进行收费,我们可以申请获得每月免费的额度。

百度智能云地址:
https://login.bce.baidu.com/

首先我们去到百度智能云里,在左边菜单栏的产品服务中搜索"文字识别",点击【领取免费资源】,将所有的免费资源领取,领取之后要半个小时之后才能生效。

回到主面板,我们要进行创建应用,填写相关的信息即可创建成功,应用创建好之后会生成AppID、API Key和Secret Key,使用百度AI的SDK,使用API Key和Secret Key参数接口调用处理!

2、项目测试/接口调用

创建一个Winform的测试项目及性能接口的调用测试,在Nuget上添加对应的Baidu.AI的SDK包,以及Json.NET的类库,如下所示。

设计一个界面,对图片文件的内容进行识别处理。

具体测试的代码如下所示。

        private void btnLoadImage_Click(objectsender, EventArgs e)
{
var imageFile = FileDialogHelper.OpenImage(false, "");if(imageFile != null)
{
var str =BaiduOcr(imageFile);//var str = TesseractOcr(imageFile); this.txtResuilt.Text =str;
}
}
private static string BaiduOcr(stringimageFile)
{
var APIKey = "**********";var SecretKey = "********";var ocr = newBaidu.Aip.Ocr.Ocr(APIKey, SecretKey);var currentImage =FileUtil.FileToBytes(imageFile);var result =ocr.Accurate(currentImage);string str = string.Empty;var txts = (from obj in (JArray)result.Root["words_result"]select (string)obj["words"]).ToList();string startString = "手术助手";int startIndex = 0;for (int i = 0; i < txts.Count(); i++)
{
if (txts[i] ==startString)
{
startIndex
= i; //记录二维表的开始位置 }
}
var newList = txts.Skip(startIndex + 1).ToList();

str
= string.Join("\r\n", newList);returnstr;
}

如系统界面的图片如下所示。

使用百度OCR接口进行测试,可以看到具体的测试结果即可,然后根据结果进行格式化的转换就可以输出对应Excel格式或者其他格式了

能够获取列表的记录,进行相应的处理即可,这样可以减少数据提取或者录入的时间,通过OCR的自动化处理,可以极大的提高数据的处理效率。

今年开年,最火的莫过于ChatGPT的相关讨论,这个提供了非常强大的AI处理,并且整个平台也提供了很多对应的API进行接入的处理,使得我们可以在各种程序上无缝接入AI的后端处理,从而实现智能AI的各种应用。ChatGPT的API可以在前端,以及一些后端进行API的接入,本篇随笔主要介绍基于ChatGPT的API的C#接入研究。

1、ChatGPT的介绍

ChatGPT(全名:Chat Generative Pre-trained Transformer),美国OpenAI研发的聊天机器人程序 ,于2022年11月30日发布。ChatGPT是人工智能技术驱动的自然语言处理工具,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,真正像人类一样来聊天交流,甚至能完成撰写邮件、视频脚本、文案、翻译、代码,写论文 等任务。

ChatGPT是美国人工智能研究实验室OpenAI新推出的一种人工智能技术驱动的自然语言处理工具,使用了Transformer神经网络架构,也是GPT-3.5架构,这是一种用于处理序列数据的模型,拥有语言理解和文本生成能力,尤其是它会通过连接大量的语料库来训练模型,这些语料库包含了真实世界中的对话,使得ChatGPT具备上知天文下知地理,还能根据聊天的上下文进行互动的能力,做到与真正人类几乎无异的聊天场景进行交流。ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

ChatGPT 的官网地址如下:
https://platform.openai.com/
,如果我们需要使用它的对话处理以及强大的API能力,需要注册才能使用,至于如何注册以及接收验证码的操作,请百度一下一下即可,这里忽略,只是介绍它的功能以及如何介入使用。

ChatGPT 可以做很多不同类型的工作,常规的问答聊天,编写各种语言的应用代码,编写论文、摘要等,以及图片处理,翻译等等,如下是它们的一些功能分类和介绍。

如我们可以在对话中测试其聊天/应答功能。

我们也可以让它编写一段操作代码,如下所示。

如果我们要了解ChatGPT平台的API介绍,可以参考
https://platform.openai.com/docs/api-reference/introduction
,其大概分类也是很常规的功能应用相关的,如下是它的列表介绍。

我们要学会如何使用API的话,需要了解它的相关模型概念,以及对各种处理的定义。

2、基于ChatGPT的API的C#接入

从上面的API介绍中,我们可以看到,API平台没有针对不同的语义给出不同的实现方式,只是给出了一个通用的调用方式,主要是基于输入参数,响应的数据格式的参考,具体的不同语言的应用如C#的接入,需要根据实际的格式进行对接。

主要的代码如下所示。

usingRestSharp;usingSystem;namespaceGpt3APIExample
{
classProgram
{
static void Main(string[] args)
{
//OpenAI API地址 string apiUrl = "https://api.openai.com/v1/engines/gpt-3/jobs";//OpenAI API密钥 string apiKey = "YOUR_API_KEY";//创建一个RestClient对象 var client = newRestClient(apiUrl);//创建一个RestRequest对象 var request = newRestRequest(Method.POST);//在请求头中添加API密钥 request.AddHeader("Authorization", "Bearer" +apiKey);//添加请求内容 request.AddJsonBody(new{
model
= "text-davinci-002",
prompt
= "What is the capital of France?",
max_tokens
= 100,
n
= 1,
stop
= null,
temperature
= 0.5,
});
//发送请求并获取响应 IRestResponse response =client.Execute(request);//显示响应内容 Console.WriteLine(response.Content);
Console.ReadLine();
}
}
}

这个代码主要就是针对输入信息和输出的内容进行简单的包装,使用 RestSharp 的一个Http类库进行访问的包装。

而为了更好的使用ChatGPT的API处理,我们可以使用更高级一点的类库,如下面介绍的两个开源C#包装ChatGPT的类库:

https://github.com/OkGoDoIt/OpenAI-API-dotnet

https://github.com/RageAgainstThePixel/OpenAI-DotNet

我觉得他们都是很不错的,都是基于ChatGPT的API格式进行了一定程度的面向对象的包装,使得我们基于C#开发起来更方便。

例如我们基于
https://github.com/RageAgainstThePixel/OpenAI-DotNet

来查看使用代码,它的介绍也比较详细,基本上覆盖到了各个方面。

我们可以模仿来生成自己的代码即可,如下所示。

    /// <summary>
    ///基于OpenAI-DotNet的使用/// </summary>
    public classTest2
{
public static asyncTask ExcuteCode()
{
var api = new OpenAIClient(newOpenAIAuthentication(Constants.ApiKey));var result = await api.CompletionsEndpoint.CreateCompletionAsync("读取图片文件的展示到窗体的C#代码", null, null, 1000, 0.1);
Log.Information(result.ToJson());
}

运行效果,可以看到输出的结果。

对于图片的生成和处理,我们也可以按照类似的API进行调用接口,如下是一段寻找图片的代码。

            var results = await api.ImagesEndPoint.GenerateImageAsync("中国布达拉宫的全景图片", 1, ImageSize.Large);foreach (var item inresults)
{
Log.Information(item);
}

为了验证图片的AI处理,我们生成两张正方形的图片,图片右下角扣掉一块空白的图片,图片格式使用PNG格式,然后调用如下代码进行替换处理。

            var imageAssetPath = "images/休息区.png";var maskAssetPath = "images/休息区2.png";var imageResults = await api.ImagesEndPoint.CreateImageEditAsync(Path.GetFullPath(imageAssetPath), Path.GetFullPath(maskAssetPath), "阳光明媚的室内休息区,有一个鸭子在池塘中", 1, ImageSize.Small);foreach (var item inimageResults)
{
Log.Information(item);
}

生成后的图片效果如下所示。

生成的空白地方,填入一个所需要的区域,红色框是我加上去醒目的。

在我们开发开发H5程序或者小程序的时候,有时候需要基于内置浏览器或者微信开发者工具进行测试,这个时候可以采用默认的localhost进行访问后端接口,一般来说没什么问题,如果我们需要通过USB基座方式发布到手机进行App测试的时候,那就需要使用局域网的IP地址了,否则就无法访问后端的接口,本篇随笔总结在开发H5程序或者小程序的时候,后端Web API项目在IISExpress调试中使用IP地址和localhost地址并存,便于开发测试。

1、修改VS配置地址文件

默认我们IISExpress是采用localhost进行调试的,我们如果需要使用局域网地址如192.1068.1.*开始的IP端进行调试,那么就需要首先修改VS的配置信息,一般在项目的隐藏文件夹 .vs 中,找到对应项目的解决方案目录下的config目录,如我一个项目是:

打开这个文件,可以看到很多配置信息,定位到binding的配置项目中,找到对应项目的配置信息,如下所示。

<sitename="WebAPI"id="3">
    <applicationpath="/"applicationPool="Clr4IntegratedAppPool">
        <virtualDirectorypath="/"physicalPath="E:\个人应用程序\Web开发框架\Bootstrap开发框架\WebAPI" />
    </application>
    <bindings>
       <bindingprotocol="http"bindingInformation="*:27206:localhost" /> </bindings> </site>

我们可以修改它,增加对应的局域网IP地址的配置信息,如下所示。

<sitename="WebAPI"id="3">
    <applicationpath="/"applicationPool="Clr4IntegratedAppPool">
        <virtualDirectorypath="/"physicalPath="G:\个人应用程序\Web开发框架\Bootstrap开发框架\WebAPI" />
    </application>
    <bindings>
<bindingprotocol="http"bindingInformation="*:27206:192.168.1.103" />
<bindingprotocol="http"bindingInformation="*:27306:localhost" />
</bindings> </site>

这里增加了一个局域网的IP配置项目,同时新建一个本地localhost的端口,因为我们希望保留两个不同的连接,如果只需保留局域网,那么移除其中一个localhost的配置即可。

保存文件就可以继续下一步了。

2、以管理员方式启动VS并进行查看

如果我们需要启动局域网IP的IISExpess端口,一定需要以管理员方式启动VS,否则IISExpess提示出错,无法绑定IP端口的。

然后在C#的Web API项目上,右键出现的菜单中,启动调试或者启动项目即可。

项目顺利启动后,就可以看到有两个不同的端口,一个是局域网IP的,一个是localhost的了。

这样我们在前端项目上,就可以配置基础路径为局域网IP地址了。

baseUrl: 'http://192.168.1.103:27206'



这样我们就可以在HBuilderX中进行扫码测试功能了,或者在微信开发者工具中也可以。

如果通过USB基座发布到手机真机测试也可以正常访问局域网的后端接口数据了。

运行程序可以获得APP的体验效果。

3、后端Web API的CORS跨域处理。

有时候,我们需要在后端的Web API项目上设置CORS的跨域处理,以便于本地调试的访问处理。

如果是常规的Web API项目,我们设置其WebApiConfig文件就可以了,如下所示。

增加CORS的工厂处理,设置相关的参数。

namespaceWebAPI
{
public static classWebApiConfig
{
public static voidRegister(HttpConfiguration config)
{
//Web API 配置和服务//支持CORS//config.EnableCors(); config.SetCorsPolicyProviderFactory(newCorsPolicyFactory());
config.EnableCors();

完整的文件如下所示。

namespaceWebAPI
{
/// <summary> ///JS跨域工厂类/// </summary> public classCorsPolicyFactory : ICorsPolicyProviderFactory
{
ICorsPolicyProvider _provider
= newMyCorsPolicyAttribute();publicICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
{
return_provider;
}
}

[AttributeUsage(AttributeTargets.Method
| AttributeTargets.Class, AllowMultiple = false)]public classMyCorsPolicyAttribute : Attribute, ICorsPolicyProvider
{
privateCorsPolicy _policy;publicMyCorsPolicyAttribute()
{
_policy
= newCorsPolicy
{
AllowAnyMethod
= true,
AllowAnyHeader
= true,
SupportsCredentials
= true,
AllowAnyOrigin
= true,//设置所有的都可以 };//Add allowed origins. _policy.Origins.Add("http://localhost:8080");
_policy.Origins.Add(
"http://localhost:8081");
_policy.Origins.Add(
"http://192.168.1.103:8080");
_policy.Origins.Add(
"http://192.168.1.103:8081");//_policy.Origins.Add("http://localhost"); }public Task<CorsPolicy>GetCorsPolicyAsync(HttpRequestMessage request)
{
returnTask.FromResult(_policy);
}
public Task<CorsPolicy>GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
returnTask.FromResult(_policy);
}
}
}

以上就是在开发前后端项目中的一些调试测试细节地方,供参考。

现在的很多程序应用,基本上都是需要多端覆盖,因此基于一个Web API的后端接口,来构建多端应用,如微信、H5、APP、WInForm、BS的Web管理端等都是常见的应用。本篇随笔概括性的介绍基于HBuilderX+UniApp+ThorUI的手机端前端开发处理,总结一下开发工具的设置,以及常见的H5端开发的一些相关知识。

1、前后端分离的后端API架构设计

在我们设计好一个Web  API 的接入层后,Web API根据用户、账号登录后获得相关的令牌,每次访问接口,通过令牌进行识别用户身份,从而提供用户身份相关的数据或者记录用户操作信息。

后端的WebAPI,这里可以是基于我们自己的技术路线,如Asp.net /Asp.net Core 等的.net路线,或者java路线,不管哪种后端技术架构,都是基于对常规业务的分类,横向或者纵向切分业务,并在后端对基础接口的提供进一步的封装处理,以便提供统一的接口接入处理。

后端采用基于Asp.net的Web API技术,可以提供按Area域来管理API的分类,Web API如下架构所示。

为了更好的进行后端Web API控制器的相关方法的封装处理,我们把一些常规的接口处理放在BaseApiController里面,而把基于业务表的操作接口放在BusinessController里面定义,如下所示。

通过Web API接口返回结果的统一封装处理,我们定义了相关的格式如下所示。

2、Vue 前端H5应用的开发

前端开发,可以是基于Vue&Element的管理后台的前端开发,也可以是Vue + UniApp+手机端组件库的开发H5或者App应用,技术路线都是基于Vue的,我们这里主要介绍UniApp+HBuliderX+Vue+ThorUI来开发H5端的应用。

UniApp
是一个使用
Vue.js
开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝)、快应用等多个平台。

使用UniApp的主要原因是它的生态比较完善,而且提供了不同平台的统一的接口调用方法,因此非常方便使用它来统一构建多端应用。

使用UniApp,为了集成方便,一般也会使用HBuilderX开发工具来进行前端的开发。
HBuilderX
编辑器是DCloud全新推出的一款HTML5的Web开发工具。HBuilder目前有两个版本,一个是windows版,一个是mac版。下载的时候根据自己的电脑选择适合自己的版本即可。如果不考虑深度集成的原因,还可以考虑使用更加广泛的VS Code编辑器,毕竟VS Code可是前端界的开发标准工具了。

另外UniApp也有自己的组件库,同时也做的很不错,不过由于参照模板的问题,我们这里使用了会员版的
Thor UI
,可以参考的案例更多,方便做出更好的界面效果。

Thor UI 是一款开发移动端网页应用、小程序的前端 UI 组件库,uni-app 版本还可以编译成为安卓/ iOS APP 应用,特点是轻量、简洁,组件覆盖比较全面,使开发更高效。Thor UI目前有微信小程序原生版本 (opens new window)和uni-app版本。

构建基于Vue的前端项目,我们有一个很重要的部分,就是需要准备好对应Web API的前端JS端类的封装处理,引入了ES6类的概念实现业务基类接口的统一封装,简化代码。

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

其中JS类的BaseApi具有常规的增删改查接口,如下所示。

代码视图如下所示,主要是定义一些通用的操作方法以及常规API接口的操作方法

如前端API模块中,会存在很多类似的JS类,如下图所示。

这样继承的关系,可以尽可能的简化代码的处理,类似类的抽象封装,可以减少大多数的常规代码,我们只需要继承基类就可以获得大类通用的API接口了。

例如,对于常用业务的组织机构类,我们只需要添加一些特殊接口的定义就可以了,其他的会继承自BaseApi的基类方法。

3、注入一些快捷的操作类入口

有时候,为了便于对一些常用类的操作,我们往往会通过Vue.prototype的方式来注入一些操作类入口,便于快捷处理。

import Vue from 'vue'Vue.config.productionTip= falseVue.prototype.http=http
Vue.prototype.tui
=tui
Vue.prototype.$store
= store

或者我们也可以把它独立在一个类文件里面进行引入,这里借鉴了uview项目的做法,把自定义对象$u挂载到uni对象上,便于操作处理。

有了这些快捷的挂载类,我们可以方便的访问一些常用的处理方法。

computed: {
currentUser() {
if (uni.$u.test.isEmpty(this.creator)) {return this.vuex_user?.info?.fullName;
}
else{return this.creator;
}
},
currentDate() {
if (uni.$u.test.isEmpty(this.createTime)) {returnuni.$u.time.timeFormat();
}
else{return uni.$u.time.timeFormat(this.createTime);
}
}
},
checkCode() {var code = this.formData.shopCode;if (uni.$u.test.isEmpty(code)) return;

shop.CheckCodeValid(code).then(res
=>{if(res.success) {this.tui.toast('未被占用, 可以使用!')
}
else{
uni.$u.toast(
"已被占用,请使用其他代码")
}
})
},

4、创建页面组件

我们知道,Vue的强大,其中之一就是它的组件化非常便利,因此我们在设计界面的时候,如果重复出现,尽可能的把它抽取出来作为组件来定义,然后提供相关的Props参数或者处理事件就很好了,而且可以减少主要页面的代码,同时也方便维护组件。

我往往在开发前端项目的时候,会抽取很多共性的地方,作为组件,然后主页面只需要简单的调用即可,随着项目的开发,我们可能抽取更多,完善更好的组件库出来了。

例如对于一个异常信息的处理,我们整合了多个模块的内容进行展示,采用自定义组件的方式,可以减少很多繁杂的前端代码。

上面页面的大部分都是自定义组件的整合使用,如下代码截图所示。

需要使用的组件,在Vue的JS代码中导入组件即可

最后我们来看看其他前端页面的效果,供参考。

登陆界面效果

业务系统的主界面效果

业务字典管理界面

组织机构以树形图的方式进行展示,单击可以查看详细机构信息,或者在下面提供添加组织机构处理。

创建组织机构的界面。

角色管理和角色人员维护

这样通过管理员的权限来处理权限系统的基础信息,也就可以不用在依赖管理前端的操作管理。

现在的很多程序应用,基本上都是需要多端覆盖,因此基于一个Web API的后端接口,来构建多端应用,如微信、H5、APP、WInForm、BS的Web管理端等都是常见的应用。本篇随笔继续分析总结一下项目开发的经验,针对页面组件化开发经验方面进行一些梳理总结,内容包括组件的概念介绍,简单页面组件的抽取开发,以及对控件值进行更改的页面组件的处理等,希望能够对大家有所启发。

1、Vue组件的概念

组件是Vue中的一个重要概念,是一个可以重复使用的Vue实例,它拥有独一无二的组件名称,它可以扩展HTML元素,以组件名称的方式作为自定义的HTML标签。因为组件是可复用的Vue实例,所以它们与new Vue()接收相同的选项,例如data,computed、watch、methods以及生命周期钩子等。

组件是可复用的 Vue 实例, 把一些公共的模块抽取出来,然后写成单独的的工具组件或者页面,在需要的页面中就直接引入即可那么我们可以将其抽出为一个组件进行复用。例如 页面头部、侧边、内容区,尾部,上传图片,等多个页面要用到一样的就可以做成组件,提高了代码的复用率。

Vue的前端界面,对界面内容部分可以根据需要进行适当的拆分,也可以把通用的部分封装为组件进行使用。如果整体界面内容比较多,可以进行拆分,根据内容的展示不同,拆分为各自的组件模块,然后合并使用即可,如下所示。

在对象UML的图例中,应该是如下所示的效果图,组织机构包含组织成员和角色的内容。

在界面上,组织成员还需要添加成员的功能,同理角色也需要添加角色的处理,如下图示。

角色界面模块的内容划分图如下所示。

实际页面组件的开发,也可以按照内容的划分方式进行组件的开发,然后将它们组合起来。

如果我们开发了很多不同业务场景的组件,那么在实际页面中,就可以对它们进行组合使用,提高页面的整洁,同时也便于代码的维护。

组件的拆分和封装,是我们前端开发中非常重要的部分,也是我们快速构建复杂页面功能的,又能轻松应对的必杀技之一。

例如对于一个异常信息的处理,我们整合了多个模块的内容进行展示,采用自定义组件的方式,可以减少很多繁杂的前端代码。

上面页面的大部分都是自定义组件的整合使用,如下代码截图所示。

需要使用的组件,在Vue的JS代码中导入组件即可

2、简单页面组件的抽取开发

例如在一些新录入内容的页面中,我们往往有一些类似人员和时间的信息需要展示,可以把它做成一个简单的页面组件模块

如果用户是创建新记录的,那么显示当前登录的用户名称和当前日期,如果是已有记录,则显示记录中的用户和时间即可,一个很简单的例子。

<!--通用填报信息展示,用于创建新记录或者明细的展示-->
<template>
    <viewclass="">
        <viewclass="tui-order-info">
            <tui-list-cell:hover="false":arrow="showArrow"@click="showDetail">
                <viewclass="tui-order-title">
                    <view>{{title}}</view>
                    <viewclass="tui-order-status":style="{color:subTitleColor}">{{subTitle}}</view>
                </view>
            </tui-list-cell>
            <viewclass="tui-order-content"v-show="visible">
                <viewclass="tui-order-flex">
                    <viewclass="tui-item-title">填报时间:</view>
                    <viewclass="tui-item-content">{{currentDate}}</view>
                    <viewstyle="padding-right: 50rpx;"></view>
                    <viewclass="tui-item-title">填报人:</view>
                    <viewclass="tui-item-content">{{currentUser}}</view>
                </view>
            </view>
        </view>
    </view>
</template>

信息填报的标题中,单击可以切换折叠或者展示模式,通过事件showDetail 进行触发的,而内容这里通过visible属性进行控制,同时接收来时Props属性的detailVisible的初始化设置。

在Props的父传子属性中,我们定义了一些通用数据属性,如标题、副标题、当前时间和填报人等。

<script>exportdefault{
emits: [
'click', 'cancel'],
props: {
title: {
type: String,
default: "信息填报"},
subTitle: {
type: String,
default: ""},
subTitleColor: {
type: String,
default: ""},
showArrow: {
//默认是否展示箭头 type: Boolean,default: false},
creator: {
type: String,
default: ""},
createTime: {
type: String,
default: ""},
detailVisible: {
//默认是否展示详情内容 type: Boolean,default: true},
},

另外,我们在data中添加一个组件属性visbile属性,

data() {return{
visible:
true}
},

这是因为传入的
detailVisible
不能在组件中中修改其属性值,因此接收它的值,并让她赋值给局部的属性,这种做法是常见的处理方式。为了同时保持两个属性的一致,我们通过Created事件中初始化局部变量,并通过Watch监控数据的变化。

created() {this.visible = this.detailVisible;
},
watch: {
detailVisible(val) {
this.visible =val
}
},

当前用户名称和日期,我们判断传递的属性是否为空值,非空则使用传递的值,否则使用当前用户身份和当前日期值,因此我们通过一个计算属性来判断,如下所示。

computed: {
currentUser() {
if (uni.$u.test.isEmpty(this.creator)) {return this.vuex_user?.info?.fullName;
}
else{return this.creator;
}
},
currentDate() {
if (uni.$u.test.isEmpty(this.createTime)) {returnuni.$u.time.timeFormat();
}
else{return uni.$u.time.timeFormat(this.createTime);
}
}
},

这里面的
this.vuex_user?.info?.fullName
是我们通过Vuex的方式存储的一个当前用户身份的信息,通过?来判断是否非空。另外,我们对list-cell的事件进行处理,切换折叠和展开的处理,因此只需要设置局部变量visible的属性即可。

methods: {
showDetail() {
this.visible = !this.visible;
}
}

这样定义好的页面内容后,我们只需要把它加入到页面组件中就可以使用它了

<script>import createinfo from'@/pages/components/createinfo.vue';
export
default{
components: {
createinfo
},

HTML代码和其他简单的HTML常规组件类似。

<createinfo></createinfo>

或者传入数据处理

<createinfo:createTime="entity.createTime":creator="entity.deliverName"></createinfo>

另外,如果我们需要在内部保留一块区域给父页面定义的模板,可以使用Slot进行处理,这样除了添加了人员和日期信息,我们还可以嵌入一个更多内容信息作为区块给组件进行展示了。

<template>
    <viewclass="">
        <viewclass="tui-order-info">
            <tui-list-cell:hover="false":arrow="showArrow"@click="showDetail">
                <viewclass="tui-order-title">
                    <view>{{title}}</view>
                    <viewclass="tui-order-status":style="{color:subTitleColor}">{{subTitle}}</view>
                </view>
            </tui-list-cell>
            <viewclass="tui-order-content"v-show="visible">
               <slot#default>
                    <!--<view class="tui-order-flex">
<view class="tui-item-title">角色编码</view>
<view class="tui-item-content">123456</view>
</view>
--> </slot> </view> </view> </view> </template>

3、对控件值进行更改的页面组件的处理

除了上面的闭合组件,我们有时候也需要一个传递信息和变更v-model的值的处理,或者抛出一些事件给父页面进行通知。

例如,我们在选择一些系统数据字典项目的时候,以及在选择时间的时候,这些是比较常见的操作,我们可以结合我们的组件库,做一些简单的组件封装,从而达到简化页面使用的目的。

如果没有自定义组件的情况,我们使用字典模块的内容,需要请求对应的字典列表,然后绑定在控件Picker中,如下代码所示。

<tui-list-cellarrow padding="0"@click="showPicker = true">
    <tui-inputrequired backgroundColor="transparent":borderBottom="false"label="送货区域"placeholder="请选择送货区域"v-model="formData.deliveryArea"disabled></tui-input>
</tui-list-cell>

<tui-picker:show="showPicker":pickerData="areas"@hide="showPicker = false"@change="changePicker">
</tui-picker>

在页面的JS脚本处理中,还需要对字典类型进行取值、以及选择后修改属性值等操作处理

<script>import dict from'@/api/dictdata.js'exportdefault{
data() {
return{
showPicker:
false,
areas: [
//{text: "中国", value: "1001"}, ]
}
},
created() {
dict.GetListItemByDictType(
"送货区域").then(res =>{this.areas =res;
})
dict.GetListItemByDictType(
"客户等级").then(res =>{this.grades =res;
})
},
methods: {
changePicker(e) {
this.formData.deliveryArea =e.value;
}
}
}
</script>

一顿操作下来还是比较麻烦的,但是如果你使用自定义用户组件来操作,那么代码量迅速降低,如下所示。

<createinfo>
    <viewclass="tui-bg-img"></view>
    <dict-items-pickerv-model="formData.deliveryArea"dictTypeName="送货区域"label="送货区域"></dict-items-picker>
    <dict-items-pickerv-model="formData.grade"dictTypeName="供货档级"label="供货档级"></dict-items-picker>
</createinfo>

获得的界面效果如下所示,是不是感觉更好,代码更整洁了。

而其他部分的代码,则只是包括了引入组件的代码即可。

import dictItemsPicker from '@/pages/components/dict-items-picker.vue'exportdefault{
components: {
dictItemsPicker
},

上面组件的dictTypeName 对应的是数据库后台的字典类型名称,根据名称,从组件中调用api类获得数据即可,页面则不需要干涉这些逻辑了。其中我们自定义组件中使用 v-model 来绑定选择的值到页面的data属性中。

我们来看看整个页面组件的全部代码(内容不多,就一次性贴出来)

<!--通用字典下拉框展示-->
<template>
    <viewclass="">
        <tui-list-cellarrow padding="0"@click="showPicker = true">
            <tui-input:required="required"backgroundColor="transparent":borderBottom="false":label="labelName":placeholder="placeholder"v-model="value"disabled></tui-input>
        </tui-list-cell>
        <tui-picker:show="showPicker":pickerData="dictItems"@hide="showPicker = false"@change="changePicker">
        </tui-picker>
    </view>
</template>

<script>import dict from'@/api/dictdata.js'exportdefault{
emits: [
'click','cancel','update:value','change'],
props: {
required: {
//是否必选 type: Boolean,default:false},
dictTypeName: {
type: String,
default:""},
options: {
type: Array,
default() {return[]
}
},
value: {
type: String,
default:""},
label: {
type: String,
default:""}
},
components: {},
data() {
return{
showPicker:
false,
dictItems: []
}
},
mounted() {
if(!uni.$u.test.isEmpty(this.dictTypeName)) {
dict.GetListItemByDictType(
this.dictTypeName).then(res=>{this.dictItems=res;
})
}
else{this.dictItems= this.options;
}
},
watch: {},
computed: {
placeholder() {
return "请选择" + this.dictTypeName;
},
labelName() {
if(this.label) {return this.label;
}
else if(!uni.$u.test.isEmpty(this.dictTypeName)) {return this.dictTypeName
}
else{return ""}
}
},
methods: {
changePicker(e) {
this.$emit('change', e)this.$emit("input", e.value)this.$emit("update:value", e.value);
}
}
}
</script>

这里在内部组件的值变化的时候,通过事件 this.$emit("update:value", e.value); 进行通知更新父组件的绑定值,而 this.$emit("input", e.value)事件则是更新内部包含的input组件的值,同时提供一个change的事件进行完整的通知处理。

由于我们传递的是DIctTypeName,组件内部在Mounted的事件后进行获取字典的内容,并将返回值更新到内部组件中去。,如果是静态的字典集合,可以通过options 进行赋值即可。

if (!uni.$u.test.isEmpty(this.dictTypeName)) {
dict.GetListItemByDictType(
this.dictTypeName).then(res =>{this.dictItems =res;
})
}
else{this.dictItems = this.options;
}

因此该组件可以接受系统数据字典的字典类型名称,或者接受静态的字典列表作为数据源供选择需要。

整个组件内部封装了读取数据的细节以及展示控件的处理,并将新值通过事件
this.$emit("update:value"
, e.value);
的方式更新父页面的v-modal的绑定值,因此看起来和一个简单的Input的组件使用类似了。

以上就是一些简单组件的封装介绍,我们可以根据实际的需要,把我们 项目中遇到的可以封装的内容提出取出来,然后进行封装为组件的方式,会发现页面维护起来更加方便整洁了。

在组件逐步增多的情况下,我们同步完善一个简单的页面用来测试查看组件的效果,否则组件一多,记起来某个组件的效果就比较困难,我们的一个测试页面例子如下所示。