在前面随笔介绍了《
在基于ABP框架的前端项目Vue&Element项目中采用电子签章处理文件和打印处理
》的处理,有的时候,我们在流程中或者一些文件签署的时候,需要签上自己的大名,一般通过签名表的方式(银行很常见)实现电子签名的处理。本篇随笔介绍如何基于Vue &Element前端的技术实现电子签名的处理。

1、实现电子签名的组件

我们知道,很多常见的功能,我们都会引入对应的组件来做页面功能,会非常简洁方便,毕竟不用重复制造轮子。

我们在Github上可以找到很多基于Vue前端技术的界面组件,其中实现电子签名的也有好几个,大多数处理界面和功能差不多。


vue-sign-canvas
,它也是基于写字或者签名模式来实现电子签名的处理,案例可以参考地址:
https://langyuxiansheng.github.io/vue-sign-canvas/

案例本身就是一个很好的例子,提供了很多配置属性来实现不同的效果。


vue-esign
也是另一款实现个人电子签名处理的组件,也提供了类似的功能。

npm install vue-esign --save
<!--vue2-->
<vue-esignref="esign":width="800":height="300":isCrop="isCrop":lineWidth="lineWidth":lineColor="lineColor":bgColor.sync="bgColor" />
<!--vue3-->
<vue-esignref="esign":width="800":height="300":isCrop="isCrop":lineWidth="lineWidth":lineColor="lineColor"v-model:bgColor="bgColor" />

<!--isClearBgColor为false时,不必再给bgColor加sync修饰符或v-model-->

<button@click="handleReset">清空画板</button> 
<button@click="handleGenerate">生成图片</button>
data () {return{
lineWidth:
6,
lineColor:
'#000000',
bgColor:
'',
resultImg:
'',
isCrop:
false}
},
methods: {
handleReset () {
this.$refs.esign.reset()
},
handleGenerate () {
this.$refs.esign.generate().then(res =>{this.resultImg =res
}).
catch(err =>{
alert(err)
//画布没有签字时会执行这里 'Not Signned' })
}
}

界面效果如下所示。

本篇随笔基于
vue-esign
来实现电子签名处理。

2、在页面中实现电子签名

在页面中引入对应的电子签名组件

import Vue from 'vue'import vueEsign from'vue-esign' //电子签名
exportdefault{
name:
'Signature',
components: {
vueEsign
},

在页面HTML代码中,放置一块区域来实现电子签名,引入Vue-esign组件,如下代码所示。

  <divclass="sigbut">
    <divclass="tip">请在这里签名</div>
    <divclass="left-bu">
      <el-buttonclass="clear-bu"size="mini"@click="handleReset">清空签名</el-button>
      <el-buttonclass="sure"size="mini":disabled="!showSig"@click="handleGenerate">确定签名</el-button>
    </div>
  </div>
  <divclass="sig">
    <vue-esignv-if="showSig"ref="esign":width="800":height="150":is-crop="isCrop":line-width="lineWidth":line-color="lineColor":bg-color.sync="bgColor" />
    <imgv-if="!showSig":src="resultImg"alt=""class="sig-img">
  </div>
methods: {//清空画布
handleReset () {this.resultImg = ''
      this.showSig = true;this.$nextTick(() =>{this.$refs.esign.reset()
})
},
//生成签名图片 handleGenerate () {this.$refs.esign.generate().then(res =>{if(res) {this.showSig = false;this.resultImg =res;
}
}).
catch(err =>{this.$message({
message:
'请签名',
type:
'warning'});
})
},

最后得到界面效果如下所示。

由于电脑没有签名笔,因此使用鼠标胡乱画的名字,效果一般,呵呵。

如果使用签名笔来实现模拟真实的签名,其实和书写效果就很接近了,因此也可以作为一般的个人签章处理。

对于语音识别,一般有实时语音识别和语音文件的识别处理等方式,如在会议、培训等场景中,可以对录制的文件进行文字的转录,对于转录文字的成功率来说,如果能够转换90%以上的正确语音内容,肯定能减轻很多相关语音文本编辑的繁琐工作,而目前大多数语音转录的接口基本都能够保证在这个成功率上,有些甚至超过98%以上,非常不错,因为他们对于各种场景的滤波,可以更加提供文字的准确性。本篇随笔对各种语音开发平台做一个介绍,并针对依图语音API的C#封装以及调用进行语音转写进行介绍。

1、语音识别的接口提供商及API情况

语音识别有很多提供商,如常见的百度、阿里云、依图语音等,他们都提供了不同的API接口来实现外部的调用处理,本篇随笔主要针对依图语音(号称能够识别99%以上的智能转录)进行测试,对于其他类型的语音API,由于接口处理方式大同小异,并没有一一测试。

1)
百度语音转录

2)
阿里云语音转录

3)
依云语音开发平台

对于短语音的测试,依图语音的转录基本上在98%以上,偶尔有一两个词语文字因为不常见而出错而已,非常不错。

由于依图语音平台虽然提供了Java的案例代码,但是没有对应C #API调用案例,因此需要根据接口的说明进行转换。

对于依图语音的API说明,还是介绍的比较详细的,如下所示。

一般根据这些很容易编写对应的接口API封装函数,我这里主要用来封装对应的C#调用API。

2、基于依图语音API的C#封装

我们针对API进行了封装,然后在界面上进行测试对应的功能,如下Winform界面测试所示。

一个短句,发现一个错误的字,不过整体效果还是比较好,断句以及内容都算很好的。

这个项目分为三部分,第一是对请求接口数据和返回数据的对象封装(简称
DTO对象),第二是对依图长语音和短语音接口的
API接口封装,包括授权信息的处理;长语音转写的任务创建、任务查询、任务停止几个部分;以及短语音的转写处理。第三是
Winform界面的功能测试和日志处理操作,方便了解功能的实际处理情况。

【创建长语音转录任务】主要用于演示如何生成签名并调用接口创建转写任务的操作,操作后提示是否成功,并记录下任务ID,供查询任务或停止任务使用。同时在日志和文本框里面生成相关的JSON数据信息,日志在运行目录的log/log.txt中记录。

对于依图语音转换来说,我们一般使用异步的处理方式,所以更好的是等待任务处理完后回调告诉我们处理结果更好,因此适合于部署Web API的应用,用于让依图语音平台的服务器进行回调处理结果。

首先第一步我们需要处理API的授权签名,用于后续接口的调用,我们可以根据签名的规则进行编写代码即可。

    /// <summary>
    ///签名处理/// </summary>
    public classSignatureHelper
{
/// <summary> ///生成授权签名信息/// </summary> /// <param name="data"></param> /// <param name="key"></param> /// <returns></returns> public static string CalculateRFC2104HMAC(string data, stringkey)
{
key
= key ?? "";var encoding = newSystem.Text.UTF8Encoding();byte[] keyByte =encoding.GetBytes(key);byte[] messageBytes =encoding.GetBytes(data);using (var hmacsha256 = newHMACSHA256(keyByte))
{
byte[] hashmessage =hmacsha256.ComputeHash(messageBytes);
StringBuilder builder
= newStringBuilder();for (int i = 0; i < hashmessage.Length; i++)
{
builder.Append(hashmessage[i].ToString(
"x2"));
}
returnbuilder.ToString();
}
}
/// <summary> ///生成请求授权信息/// </summary> /// <returns></returns> public staticAuthRequestDto GetAuthDto()
{
var builder = newConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(
"appsettings.json", true, reloadOnChange: true);var config =builder.Build();//读取配置 var DevId = config["YITU:DevId"];var DevKey = config["YITU:DevKey"];var dto = newAuthRequestDto();
dto.DevId
=DevId;
dto.Timestamp
=DateTime.Now.DateTimeToInt();var accessKey =DevKey;var signKey = dto.DevId + dto.Timestamp; //签名加密键 dto.Signature =SignatureHelper.CalculateRFC2104HMAC(signKey, accessKey);returndto;
}
/// <summary> ///对请求的HttpClient设置相关的授权请求Header信息/// </summary> /// <param name="client"></param> /// <param name="dto"></param> public static voidSetSigature(HttpClient client, AuthRequestDto dto)
{
client.DefaultRequestHeaders.Add(
"x-dev-id", dto.DevId);
client.DefaultRequestHeaders.Add(
"x-request-send-timestamp", dto.Timestamp.ToString());
client.DefaultRequestHeaders.Add(
"x-signature", dto.Signature);

client.DefaultRequestHeaders.Add(
"Date", DateTime.Now.AddHours(-8).ToString("R"));
}
}

这个按钮功能,主要测试一下使用辅助类生成的授权认证信息,授权认证信息是根据加密规则构建的签名信息和账号信息。

这个主要是演示辅助类生成签名,并获得的数据信息:

然后我们定义一个控制器,用于接收依图语音的服务器API回调处理,用于记录转换的结果内容。

namespaceCallBackApi.Controllers
{
/// <summary> ///用于接收长语音信息回调的处理/// </summary> [ApiController]
[Route(
"[controller]")]public class LongVoiceController : ControllerBase

在控制器中编写存储的逻辑,写入数据库。

        /// <summary>
        ///回调写入结果, 路由:post /longvoice/// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
[HttpPost]public async Task<CommonResultDto>CallBack(LongVoiceResultDto input)
{
CommonResultDto result
= null;var json =JsonConvert.SerializeObject(input, Formatting.Indented);
_logger.LogInformation(json);
try{var helper = newLongVoiceDbHelper();var bResult = awaithelper.SaveData(input);
result
= new CommonResultDto(0, bResult ? "操作成功" : "操作失败");
}
catch(Exception ex)
{
result
= new CommonResultDto(1, ex.Message);
_logger.LogError(ex, ex.Message);
}
returnresult;
}

我们根据依图语音的对象模型,构建对应的对象信息属性,然后保存起来即可。

        /// <summary>
        ///保存数据到数据库/// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        public async Task<bool>SaveData(LongVoiceResultDto dto)
{
bool result = false;if(dto != null)
{
using(var db =CreateDb())
{
var info = newConsultationInfo();
info.DiscernStatus
=dto.taskId;
info.OperateStatus
= "未识别";if (dto.data != null && dto.data.speechResult != null)
{
if (dto.data.statusCode == 3)
{
info.OperateStatus
= "已识别";
}
var speechResult =dto.data.speechResult;
info.DiscernText
=speechResult.resultText;
}

result
= await db.Insertable(info).ExecuteCommandAsync() > 0;
}
}
returnresult;
}

回调接口主要用于长语音任务执行完成后,服务器的回调处理,我这里使用.net core5的Web API项目来处理,通过使用Restful规则的API控制处理,接收服务器的回调数据请求,并把请求数据记录数据库或者在log/log.txt文件中。

我们先发起语音转录请求,如下代码所示。

       privateLongVoiceRequestDto GetVoice1()
{
var input = newLongVoiceRequestDto()
{
audioUrl
= "https://www.***.com/downloads/iqidi.mp3", //语音文件 callback = "http://www.***.com:8080/longvoice", //回调地址 fileData = newFileData
{
aue
= "mpeg2",
audioName
= "iqidi.mp3",
sampleRateHertz
= 16000,
lang
= "MANDARIN"},
speechConfig
= newSpeechConfig
{
scene
= "GENERAL",
byWords
= true,
customWords
= new List<string>()
{
//"开发框架提供商",//"广州爱奇迪软件科技有限公司" },
useCustomWordsIds
= new List<int>()
{
},
addPunctuation
= true,
wordsReplace
= true,
numOfSpeakers
= 1,
convertNumber
= true,//disfluency = true },
wordsReplace
= new List<WordsReplace>()
{
newWordsReplace()
{
keywords
= "他妈的",
replace
= "*"},newWordsReplace()
{
keywords
= "破坏",
replace
= "##"}
}
};
returninput;
}

对于长语音的转换,我们只是发起请求,有时候需要等待一点时间,服务器才会根据请求的回调地址进行会写操作的。发起长语音的请求如下代码所示。

        private async void btnTransfer_Click(objectsender, EventArgs e)
{
try{var input = this.radSmall.Checked ?GetVoice1() : GetVoice2();var result = awaitlongApi.Transfer(input);if (result != null)
{
this.txtTaskId.Text = result.taskId;//任务ID,可供查询,停止 MessageUtil.ShowTips($"任务创建{(result.rtn == 0 ?"成功":"异常:"+ result.message)},任务ID:{result.taskId}");
}
var json =JsonConvert.SerializeObject(result, Formatting.Indented);this.richLog.AppendText(json);this.richLog.AppendText(Environment.NewLine);
Serilog.Log.Information(json);
}
catch(Exception ex)
{
MessageUtil.ShowError(ex.Message);
}
}

回调接口主要用于长语音任务执行完成后,服务器的回调处理。

而如果是短语音转写,就和我们前面说到的一样,马上就可以出结果了。

        private async void btnShort_Click(objectsender, EventArgs e)
{
try{var input =GetShortVoice();var result = awaitshortApi.Transfer(input);if (result != null)
{
MessageUtil.ShowTips($
"短语音听写, 操作{(result.rtn == 0 ?"成功":"异常:"+ result.message)}");
}
var json =JsonConvert.SerializeObject(result, Formatting.Indented);this.richLog.AppendText(json);this.richLog.AppendText(Environment.NewLine);
Serilog.Log.Information(json);
}
catch(Exception ex)
{
MessageUtil.ShowError(ex.Message);
}
}

我们只需要把返回的结果信息,转换为相关的模型对象,然后进行显示或者存储即可完成。

项目可以通过VS发布方式发布一个文件包,部署到服务器上即可。

配置项目后台一直运行。

以上就是关于依图语音的测试处理,对应后端的回调处理,我们可以转录长语音文件,不过依图语音平台对于身份的审核比较严格,也是收费的API(其他阿里、百度也都是),因此在商用的时候,需要考虑好即可。

不管我们开发是基于Asp.net core 还是.net core,或者还是.net framework的相关应用,如果有一个比较全面的公用类库以及相关的扩展函数来支撑,我们开发项目上会节省很多相关的处理方法查询和封装。本篇随笔主要针对性的介绍基于一些对象进行扩展函数的封装带来的便利性进行说明,毕竟基于上下文的调用扩展函数,比不断地去自己去类库中查找那个方法合适,会更高效一些。

1、针对常规对象进行扩展函数的封装

我们常常使用到的对象,可能包括有字符串(String)、日期对象(DateTime)、列表集合(List/IEnumerable)、常规对象Object、UI控件(如普通的Control对象处理,其他常规控件对象等),一般来说,这些常规类型的对象我们会在代码中应用很多,对于上下文的一些快速的调用可以快速减少我们翻查代码的时间,随着时间的累积,我们会使用的更加快捷和熟悉。

字符串可能是我们日常使用C#代码,操作最多的一个.net 基础类型了,一般字符串涉及到格式判断、格式转换、内容截取、正则表达式处理、编码处理等等,几乎是五花八门。

我们一般为了方便,都会给字符串类型编写一些扩展函数,以便于常规的处理。编写扩展字符串函数的处理也很简单,创建一个静态类,并在方法中第一个方法参数指定为this即可,如下所示。

我们可以编写很多用于常规处理的字符串函数,如格式转换和内容提取,以及正则表达式等。

字符串的格式转换是非常常见的处理,如对UrlEncode/HtmlEncode/Base64转换/byte[]字节转换等等,一般我们可以把常见的放在一个静态类里面,这样便于维护。

日期类型也是我们常见的,因此可以针对日期的格式转换、日期的处理等内容整合到一个扩展函数里面,便于快速的调用和维护。

针对常规的C#对象的扩展,我们编写、收集整理了不少扩展函数,主要就是提高开发的便利性。如下截图所示。

有时候,我们在开发常规Windows项目的时候,需要用到对常规控件的赋值、数据绑定等常规处理,我们也可以通过编写扩展函数,并重载不同的方法,来更加的方便调用扩展函数的代码处理。

而对于第三方的UI控件,如DevExpress的一些控件,如果处理的时候,也进行一些控制封装,会使得调用代码更加简洁的,如下是针对一些常规DevExpress控件的扩展函数封装。

2、扩展函数的调用及便利

这些不同类型的控件扩展函数的封装,使得我们操作对象的处理更加便捷,如字符串转换为int类型的时候,我们直接调用扩展函数转换即可。

        /// <summary>
        ///获取表的所有记录数量/// </summary>
        /// <returns></returns>
        public intGetRecordCount()
{
var action = "GetRecordCount";string url =GetTokenUrl(action);return JsonHelper<DataTable>.ConvertString(url).ToInt32();
}

还有我们在开发界面的时候,更加方便调用来进行数据处理。如常规的对TreeList控件的处理代码,需要添加列和属性的时候,代码如下。

        /// <summary>
        ///初始化树控件/// </summary>
        private voidInitTree()
{
this.tree.Columns.Clear();//添加显示列 this.tree.Columns.Add(new TreeListColumn { FieldName = "Id", Caption = "Id" });//增加一个隐藏的字段,存储需要的ID this.tree.Columns.Add(new TreeListColumn { FieldName = "Name", Caption = "菜单名称", Width = 160, VisibleIndex = 0});//设置树控件的层次关系及属性 tree.KeyFieldName = "Id";
tree.ParentFieldName
= "PID";this.tree.OptionsBehavior.Editable = false;this.tree.OptionsView.EnableAppearanceOddRow = true;this.tree.OptionsView.EnableAppearanceEvenRow = true;this.tree.OptionsDragAndDrop.DragNodesMode = DragNodesMode.None;//不允许拖动

而使用扩展函数的时候,可以简化它的调用方式,并比较直观的处理。

       /// <summary>
        ///初始化树控件/// </summary>
        private voidInitTree()
{
this.tree.Columns.Clear();this.tree.OptionsDragAndDrop.DragNodesMode = DragNodesMode.None;//不允许拖动//控件扩展函数封装处理 this.tree.CreateColumn("Name", "菜单名称", 160, true);this.tree.InitTree("Id", "PID", null, false, false);

详细了解这个处理,可以参考随笔《
在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件

有时候,我们还可以结合数据库处理,对扩展函数进行一定的封装处理,如下代码所示。

        /// <summary>
        ///绑定下拉列表控件为指定的数据字典列表/// </summary>
        /// <param name="combo">下拉列表控件</param>
        /// <param name="dictTypeName">数据字典类型名称</param>
        /// <param name="defaultValue">控件默认值</param>
        public static void BindDictItems(this ComboBoxEdit combo, string dictTypeName, stringdefaultValue)
{
Dictionary
<string, string> dict = BLLFactory<DictData>.Instance.GetDictByDictType(dictTypeName);
List
<CListItem> itemList = new List<CListItem>();foreach (string key indict.Keys)
{
itemList.Add(
newCListItem(key, dict[key]));
}

BindDictItems(combo, itemList, defaultValue);
}

用的时候,就非常简单了,如下代码是实际项目中对字典列表绑定的操作,字典数据在字典模块里面统一定义的。

/// <summary>
///初始化数据字典/// </summary>
private voidInitDictItem()
{
txtInDiagnosis.BindDictItems(
"入院诊断");
txtLeaveDiagnosis.BindDictItems(
"最后诊断");//初始化代码 this.txtFollowType.BindDictItems("随访方式");this.txtFollowStatus.BindDictItems("随访状态");
}

这样就非常简化了我们对字典数据源的绑定操作了,非常方便易读,下面是其中一个功能界面的下拉列表展示。

扩展函数的功能主要即使便于我们对一些常规类型的对象进行快速的操作,因此积累丰富的扩展类库,可以提高我们的开发能力,同时也可以给我们提供很好的参考,有时候可以把一些新的内容加入到已有的扩展函数中去,随着逐渐对对象的处理函数更加熟悉,我们就会更加娴熟的使用它们。

在我们实际业务开发中,从头开发一个应用系统,不管是基于BS的前端项目,还是基于Winform的CS应用系统,都是由容易到复杂,逐步演化的一个开发过程,如果我们基于一定基础上,并配合一些配套的开发工具,那么这个应用系统的开发就会变得容易很多,毕竟现在每一个应用系统都是要求要五脏俱全的系统,不仅要长得漂亮,还要有内涵。对于一个基于CS模式的Winform应用系统,开发起来相对比较容易一些,依赖的东西的边界比较清晰一些,因此大多数可以使用独立、模块化的方式来开发系统。

1、Winform的应用系统的模块化场景

我们开发一个系统的时候,往往需要对各种资源进行各种处理,如硬件API接口(摄像头、读卡器、采集器等)、数据库访问(SQLServer、Oracle、Mysql、SQLite、PostgreSQL、MongoDB)、文件读写支持(XML、JSON、文本文件、图片等)、缓存处理(MemoCache、Redis等),以及第三方的一些接口支持(如微信支付、支付宝、抖音等),当然还有自己业务内部的接口支持,涉及的范围很大也很广,不过我们一般来说,一个系统的主要业务还是对数据库的存取进行处理,因此诞生了很多基于数据库业务的基础模块,如基于通用字典读取和设置的字典模块、权限管理模块等,这些应用模块,基本上是各个业务系统都需用到的,不同的系统,他们的规则和显示方式可能有所差异。

如下是一个基础Winform框架支持的一些模块图示。

如通用字典管理着整个系统的各种下拉列表或者参考信息。

权限管理模块则定义好整个组织机构、用户、角色、权限分配、菜单资源定义、字段权限、数据权限、黑白名单等配置信息。

权限模块中,是相对复杂一点的模块,其中还需要管理各级组织机构的信息。

权限管理系统模块,可以单独调用登陆后进行展示,也可以通过界面整合的方式,无缝的整合到应用系统中去,作为一个整体应用对待使用。

而工作流模块则管理着日常流程表单的处理。

业务模块则是基于我们某个业务范畴开发的一个独立模块,可能应用到了很多通用的模块,这个也是模块化的精髓。

如下是整合以上一些基础模块的CRM客户关系管理系统的内容,也可以简单的把CRM当成一个复杂一点的业务应用模块。

模块化类似小孩子搭积木的方式构建一个所要的形状,虽然软件和建筑在这方面肯定更加复杂化,但是模块化系统是大势所趋,也是简化开发、易于维护、提高系统健壮性的重要举措。

随着应用模块的不断开发,有一个好的组织架构,纵向统一管理模块中各个局部分层,会更加方便,因此引入了一个框架的设计模式,以便更好统一各个分层的管理。

Winform开发架构如下所示。

一般框架都支持常见的关系型数据库,并提供他们对应的支持切换。

另外,一方面,我们可以收集WInform开发中常常涉及到的界面控件使用,类库使用,常规组件使用等一系列的代码片段、难点疑点进行整合管理,整理一个综合的案例提供各种各样的开发测试案例代码,用来辅助开发学习了解。

以及一些杂项的内容收集。

力求在各个方面能够快速辅助系统的开发。

2、Winform的应用系统的开发

在我们实际不断的迭代开发中,往往会不断的累积出各个完善的应用模块出来,不过这个过程开发,可以基于我们的配套开发工具进行快速的开发,由于各个模块是基于相同的架构分层,以及对整套系统的一个固定的开发模式,我们可以采用代码生成工具来根据数据库信息,生成对应分层的项目代码,并把它们之间的关系处理好,生成一个完整的解决方案,生成即可编译通过的完善项目模块。

对于Winform界面来说,有时候,我们需要精细化的定义各种界面部分的内容,因此工具提供配置进行定义,然后再生成。

如在代码生成工具里面,设置主从表的界面生成如下所示。

有了系统框架的通用基础模块支持,以及基于固定架构的代码生成工具辅助开发,事半功倍,而且开发的系统模块更加统一和稳定,并且内置的支持多种数据库的接入, 基本上能够覆盖大多数应用系统的需要。

基于Vue的前端框架有很多,这几年随着前端技术的官方应用,总有是学不完的前端知识在等着我们,一个人的精力也是有限,不可能一一掌握,不过我们学习很大程度都会靠兴趣驱动,或者目标导向,最终是可以以点破面,逐步掌握各种前端知识的。本篇随笔主要以实际应用场景为例介绍一些Vue前端技术的拓展,供大家参考学习。

基于Vue的前端知识,不管在移动端,还是桌面端管理后台,都有着很多可供选择的前端框架或者组件。

移动端开发各种小程序应用和App都综合一起了,一站化处理,可以同时开发微信小程序、支付宝小程序、抖音以及百度等小程序,如UniApp、UView等都是以Vue为开发语言的,并提供了便于开发和部署的IDE工具,虽然很多也可以基于业界使用的VSCode来开发,不过定制化的工具,效率也会显得更优一些。而随着移动端小程序开发的活跃,京东凹凸实验室也近两年推出了Taro的前端框架,和UniApp等一样,也是可以同时开发微信小程序、支付宝小程序、抖音以及百度等小程序,对标各种小程序的开发的。

桌面端开发,基于Vue的桌面后台管理框架也有很多,如Element、AntDesign-Vue等,随着Vue3的推广及覆盖,会有越来越多的前端框架的推出。另外的还有
Electronjs
以及基于Vue的Electron框架,可以系统平台无关的基于浏览器的应用,相当于自带浏览器的应用程序了。

基于React前端语言的开发也是一个方向,如最早的AntDesign就是React的,后来才推出Vue版本,京东的Taro早期也是React版本,现在同时推出带有Vue语言版本的框架。本篇随笔不详细讨论各种前端的技术,主要就是介绍基于Vue开发的门户网站展示和后台数据管理系统。

1、门户网站展示和后台数据管理的需求和采用组件框架

我们不管是个人团队,还是企业,一般总有开发推广网站的需求,虽然可以制作静态的网站宣传,不过动态的内容管理更新会更加方便,也同时突显更好的交互体验,而伴随着动态的内容发布,内容管理同时也需要一个后台进行处理。

因此结合两者的特点,我们前端门户发布,采用
Bootstrap-vue
组件(中文站点:
https://code.z01.com/bootstrap-vue/
),BootstrapVue 是基于 Bootstrap v4 + Vue.js 的前端 UI 框架。它是流行的
Bootstrap 框架
与 Vue.js 的集成。使用 BootstrapVue,任何人都可以从 Vanilla.js 或 jQuery 切换到 Vue.js,而无需担心 Bootstrap 对 jQuery 的严重依赖,甚至无法找到解决方法。这就是 BootstrapVue 的救援方式。它有助于弥补这一差距,并允许 Vue 开发人员能够轻松地在他们的项目中使用 Bootstrap。BootstrapVue不依赖Jquery。

GitHub库的地址:
https://github.com/topics/bootstrapvue

BootstrapVue的官网地址(可能受限无法访问):
https://bootstrap-vue.org/

BootstrapVue的中文网站地址如下:
https://code.z01.com/bootstrap-vue/

通过在Vue项目中引入对应的 BootstrapVue,那么它的相关组件使用就参考官网的介绍了解即可。BootstrapVue中有很多和Bootstrap一样的组件,不过标签前缀需要加上b-

例如对于常用的按钮界面代码处理,如下所示。

<div>
  <b-button>Button</b-button>
  <b-buttonvariant="danger">Button</b-button>
  <b-buttonvariant="success">Button</b-button>
  <b-buttonvariant="outline-primary">Button</b-button>
</div>

Bootstrap的CSS已经广泛应用了很多年了,有着很成熟的技术体系,因此Bootstrap的页面资源也非常的多。我们在网上搜一下Bootstrap,就可以找到很多相关Bootstrap的模板提供网站,从中我们可以下载或者借鉴很多设计良好的界面,从而利用来开发属于自己的公司门户网站内容。

后端管理,我们采用比较广泛的Vue+Element的后台框架,使用
Element
强大的组件能力,是我们更加方便的管理门户网站后台的数据,目前Element的版本已经推出基于Vue3.x的
Element-Plus
版本了。Element提供了非常丰富的数据管理组件,能够为我们的后台管理提供非常高效、便捷的解决方案。

门户网站前端一般负责合理展示内容,而后端就需要整合数据模型,尽可能的把内容管理得更加方便一些,我们把几个模块的内容一起综合管理,根据不同的类别进行区分即可,因为大多数情况内容模型的格式是相似的。

我们创建了几个管理菜单入口,用于维护管理门户网站的内容信息,如下导航所示。

利用Vue和Element的友好协调性,我们可以快速基于这些门户网站的数据表进行业务模块的开发管理。

公司门户动态网站预览地址:
http://www.iqidi.com:8000

2、前端门户网站的模块设计及界面

一般的公司门户网站,都可能包含一些相关的产品、解决方案、客户或合作伙伴、图片展示、公司介绍、公司新闻等等。首页里面可能放置一些简略的综合内容,如下所示

门户网站一般菜单放在水平的放在顶部,如下所示,这样比较容易阅读。

在BootstrapVue组件库里面,提供了很多对Bootstrap同等类似的组件封装,其中图片轮播可以采用b-carousel-slide组件实现,而有一些小的图片,如客户/合作伙伴Logo或者友情连接什么的,则可以使用一个滑动图片展示的vue-awesome-swiper组件。

这样我们运行的时候 ,效果如下所示。

不同的轮播,我们指定不同的背景图片,以及文字,就很好的达到展示产品信息的目的。

有一些小的图片,如客户/合作伙伴Logo或者友情连接什么的,我们可能希望以图片的方式展示一个图片墙即可,如果内容比较多,那么占用版面比较大,而采用 vue-awesome-swiper 则可以避免这个问题,它是在展示图片的基础上,定时动态移动图片浏览,可以在更小的空间上放置更多的图片。

我们来看看具体的展示效果,其中单击图片,可以打开合作伙伴的网站地址。

这个面板在可视范围内,展示固定的几张图片,然后动态滑动,可以放置很多,又能拖动查看,效果还是非常不错的。

除了这两种,有时候我们的产品可能需要展示更多的图片,就是图片画廊的意思,那么我们就可以综合展示图片了,如下效果所示。

其中包括产品、框架等产品的介绍,维护内容差不多,只是展示方式有所差异。

框架产品的介绍,和软件产品的类似,不过这里采用了大图展示的方式,栅格的大小也有所变化。

以上这些网站门户的内容,由于采用了BootstrapVue,因此也是自适应的窗口布局的,我们可以切换到手机屏幕模式,那么可以看到正常、合理的展示,而不会仅仅是给电脑桌面端的展示。

我们可以在实现代码的时候,合理设置栅格的大小,从而让内容按照自己的需要展示。

对于门户网站,我们大概可以了解到门户网站的一些常用模块,如网站菜单、网站轮播广告、网站内容介绍(如公司信息、产品信息、合作伙伴、公司客户等)、网站图片展览、公司新闻等等模块,以及网站的一些常规设置信息,如Logo、网站名称、网站底部信息(包括公司地址、备案信息等等)。

这些模块有些可以综合一起管理,因为内容大多数格式差不多,如网站内容介绍(如公司信息、产品信息、合作伙伴、公司客户等),有些这需要独立设计管理,如网站菜单、网站轮播广告
等,
另外一些内容管理如新闻和模块内容类似,我们为了方便也将它独立进行维护即可。

综合以上一些内容,我们可以设计一些表来承载这些信息,然后在后端提供对应对口,供前端使用即可。根据需要我们设计了后端存储的数据表,如下所示

按照后端ABP框架或者Web API所需的框架类,使用代码生成工具生成代码后,集成在项目中,常规的增删改查、分页管理等基础接口就可以直接使用了。

后端API接口通过Swagger查看对应公开接口,如下所示。

由于门户网站不需要用户登录,或者授权信息,那么可以公开部分接口作为匿名访问,如访问菜单集合的信息作为匿名接口访问,如下所示。

再次定义一个API类,这个是直接访问后端接口获取数据的,如下所示

这样我们如果需要使用实际接口获取数据的话,导入对应的API类即可,如下所示。

import portal2 from '@/api/system/portal'

网站关于公司客户的动态数据展示效果如下所示。

3、后台管理模块的设计和界面

后台管理模块,主体框架界面采用的是基于后台配置的菜单动态生成,左侧是菜单,右边顶部是特定导航条和内容区

网站信息,主要就是维护LOGO、网站名称、网站底部信息的一些内容的。

网站菜单管理,综合管理门户网站中的菜单连接信息。

网站新闻,是维护新闻内容列表的。

这个是属于前端、后端完全分离的架构设计,后端采用基于Asp.net的Web API技术,并提供按域来管理API的分类,Web API如下架构所示。

而Vue&Element的前端的架构设计,也借鉴了我们ABP框架的前端管理部分,Vue&Element的前端的架构设计如下所示。

引入了前后端分离的Vue + Element 作为前端技术路线,那么前后端的边界则非常清晰,前端可以在通过网络获取对应的JSON就可以构建前端的应用了。 一般来说,我们页面模块可能会涉及到Store模块,用来存储对应的状态信息,也可能是直接访问API模块,实现数据的调用并展示。在页面开发过程中,多数情况下,不需要Store模块进行交互,一般只需要存储对应页面数据为全局数据状态的情况下,才可能启用Store模块的处理。通过WebProxy代理的处理,我们可以很容易在前端中实现跨域的处理,不同的路径调用不同的域名地址API都可以,最终转换为本地的API调用,这就是跨域的处理操作。

公司门户动态网站预览地址:
http://www.iqidi.com:8000