2023年2月

在我们基于UniApp的H5项目中,需要生成一些二维码进行展示,另外也需要让用户可以扫码进行一定的快捷操作,本篇随笔介绍一下二维码的生成处理和基于H5的扫码进行操作。二维码的生成,使用了JS文件weapp-qrcode.js进行处理,而二维码扫码则是基于一个第三方组件的方式进行支持的,最后通过统一入口来支持不同平台的扫码操作。

1、二维码的生成处理

二维码的生成,使用了JS文件weapp-qrcode.js进行处理的。因此我们在页面或者组件使用它的时候,需要引入JS文件才能调用。

import qrCodefrom '@/libs/weapp-qrcode.js';

我们为二维码的展示,创建一个自定义组件,用来展示二维码信息以及一些说明信息,如下所示。

<viewclass="hidden-box">
    <!--<view class="code-tit">二维码</view>-->
    <viewclass="qrcode-box">
        <tui-no-datav-if="!code":fixed="false"imgUrl="/static/image/img_nodata.png">暂未生成二维码</tui-no-data>
        <viewclass="qrcode"v-else>
            <canvas:style="{ width: qrcode_w + 'px', height: qrcode_w + 'px' }"canvas-id="qrcode"id="qrcode"></canvas>
        </view>
    </view>
    <viewclass="explain"v-if="desc.length > 0">使用说明</view>
    <viewclass="explain-text"v-for="(items, index) in desc":key="items">{{ items }}</view>
</view>

如果没有生成二维码的所示,我们用一个空图片代替,如下效果所示。

二维码生成的时候,接收一个控件id,以及二维码的值,生成函数代码如下所示。

    //二维码生成工具
qrcode(text, canvasId) {//console.log(text, canvasId)
        newqrCode(canvasId, {
text: text,
width:
this.qrcode_w,
height:
this.qrcode_w,
colorDark:
'#000000',
colorLight:
'#ffffff',
correctLevel: qrCode.CorrectLevel.H
});
}

在添加一个方法对展示进行折叠处理,那么具体如下所示。

showDetail() {this.visible = !this.visible;if (this.visible) {
setTimeout(()
=>{this.qrcode(this.code, 'qrcode');
},
60);
}
},

在测试页面中,导入刚才的二维码自定义组件,然后以普通的组件一样使用它即可。

<qrcode-info:code="entity.qrCode"></qrcode-info>

随便弄一个二维码的值,生成二维码后的界面测试效果如下所示。

2、二维码的扫码操作

我们知道,基于UniApp的程序中,内置有扫码操作

uni.scanCode(OBJECT)

地址是:
https://uniapp.dcloud.net.cn/api/system/barcode.html#scancode

不过该接口却不能在H5中调用扫码处理,

如果需要在H5应用中扫码,那么需要另辟蹊径,如果使用基于微信的SDK进行,还需要一系列的操作很麻烦,而且H5扫码一般需要https的支持才能调用摄像头的。

我在GitHub上搜索了一些基于H5扫码的项目,好像效果都不是很理想,估计是没有找到好的案例。在不经意间,发现《
H5调用摄像头识别二维码
》(需要https环境才能使用)效果挺好,就下来整合在项目中使用。

为了方便通用的扫码处理,我们这里定义了一个通用的扫码页面scan-qrcode.vue ,其他地方需要扫码的,统一定位到该页面处理即可。

在页面代码中,我们引入上面的二维码扫描组件即可,如下代码所示。

<template>
    <viewclass="container">
        <get-qrcode@success='qrcodeSucess'@error="qrcodeError"></get-qrcode>
    </view>
</template>

<script>import getQrcode from'@/pages/components/GetQrcode/getQrcode.vue'exportdefault{
components: {
getQrcode
},

另外定义一个to参数,用来判断页面转到那里去的。

data() {return{
to:
'' //页面带过来的to地址 }
},
onLoad(options) {
this.to = options?.to;
}

同时定义两个方法,一个是成功处理的,一个是出错的提示的。

qrcodeSucess(data) {if(uni.$u.test.url(data)) {
let url
=data;
console.log(url)
if (url.indexOf('#') > 0) {
let pageurl
= url.split('#')[1];
console.log(pageurl);
//在页面地址后增加一个to参数 let toUrl = uni.$u.test.isEmpty(this.to) ? pageurl : (pageurl + `&to=${this.to}`)
uni.navigateTo({
url: toUrl
})
}
else{
uni.$u.toast(
"URL格式不符");
}
}
},
qrcodeError(err) {
console.log(err)
uni.showModal({
title:
'摄像头授权失败',
content:
'摄像头授权失败,请检测当前浏览器是否有摄像头权限。',
success: ()
=>{
uni.navigateBack({})
}
})
},

扫码成功后,会自动根据地址进行跳转到具体的二维码URL页面,并带过去一个to的参数给目标页面。扫码的页面效果如下所示。

如果我们扫码的内容是一个URL连接,就会跳转到具体的页面中去,并且带上一个to参数用于区分目标。

这样我们在一些需要的地方,通过按钮或者图片来触发一个扫码行为即可。

处理代码如下所示

uni.navigateTo({
url:
'../common/scan-qrcode'})

如果我们的程序,需要兼容App模式、小程序模式和H5等模式,那么我们可以通过判断来切换不同的扫码处理,如下代码所示。

onScan(to) { //h5扫描二维码并解析
    console.log("不支持H5扫码 走onScan这个方法")
let url
= '../common/scan-qrcode?to=' +to;this.tui.href(url)
},
scanCode(to) {
//允许从相机和相册扫码 //#ifndef H5 uni.scanCode({
scanType: [
'qrCode', "barCode"],
success:
function(res) {
console.log(res)
//微信小程序 if (res.errMsg == "scanCode:ok") {//扫描到的信息 //uni.$u.toast(res.result); var url =res.result;if(uni.$u.test.url(url)) {
console.log(url)
if (url.indexOf('#') > 0) {
let pageurl
= url.split('#')[1];
console.log(pageurl);
//在页面地址后增加一个to参数 uni.navigateTo({
url: pageurl
+ `&to=${to}` //加上功能 })
}
else{
uni.$u.toast(
"URL格式不符");
}
}
else{
uni.$u.toast(
"URL格式不符");
}

}
else{
console.log(
"未识别到二维码,请重新尝试!")
uni.$u.toast(
'未识别到二维码,请重新尝试!')
}
}
});
//#endif //#ifdef H5 this.onScan(to)//#endif }

这样我们就统一处理入口了,如下调用代码所示。

packsign() {this.scanCode('packsign')
},

以上就是二维码生成和在H5中扫码的处理操作供参考。

在Winform开发中中,我们为了方便客户选择,往往使用系统的字典数据选择,毕竟选择总比输入来的快捷、统一,一般我们都会简单封装一下,以便方便对控件的字典值进行展示处理,本篇随笔介绍DevExpress控件的几种常见的字典绑定展示方式,希望我们在实际WInform项目中使用到。

1、常规下拉列表的处理

常规的处理方式,可能会使用ComboBoxEdit 控件来承载下拉列表,下拉列表的值可以是固定的列表,也可以来自字典的方式,具体根据实际情况而定,大概的效果如下所示。

单击下拉列表,会展示一些常规的字典项目,如下效果所示。

如果使用控件原始方式,我们绑定控件的下拉列表值的做法如下所示。

combo.Properties.BeginUpdate();//可以加快
combo.Properties.Items.Clear();
combo.Properties.Items.AddRange(itemList);
combo.Properties.EndUpdate();
//可以加快

不过我们一般倾向于高效率的界面处理,一般会编写各类型的界面控件的扩展函数用于快速处理。

不同类型的控件我们用一个独立的扩展文件来处理,这样方便维护的同时,也方便借鉴完善。

例如对于上面的控件,我们的绑定方法的扩展函数如下所示。

/// <summary>
///绑定下拉列表控件为指定的数据字典列表/// </summary>
/// <param name="combo">下拉列表控件</param>
/// <param name="itemList">数据字典列表</param>
/// <param name="defaultValue">控件默认值</param>
/// <param name="emptyFlag">是否加入空值选项</param>
public static void BindDictItems(this ComboBoxEdit combo, List<CListItem> itemList, string defaultValue, bool emptyFlag = true)
{
combo.Properties.BeginUpdate();
//可以加快 combo.Properties.Items.Clear();
combo.Properties.Items.AddRange(itemList);
if(emptyFlag)
{
combo.Properties.Items.Insert(
0, new CListItem(""));
}
if (itemList.Count > 0)
{
if (!string.IsNullOrEmpty(defaultValue))
{
combo.SetComboBoxItem(defaultValue);
}
else{
combo.SelectedIndex
= 0;
}
}

combo.Properties.EndUpdate();
//可以加快 }

其中方法增加了一些默认值以及是否追加空白项目的处理。

当然,我们为了适应各种数据源的绑定方式,我们重载了很多不同的函数处理,如下截图所示。

当然对于其他同类型的下列列表控件也是这样处理即可。这样界面上,我们就可以指定调用绑定的处理操作了

private voidInitDictItem()
{
this.txtManufacture.Items.Clear();this.txtManufacture.Items.AddRange(DictItemUtil.GetDictByDictType("供货商"));this.txtBigType.Items.Clear();this.txtBigType.Items.AddRange(DictItemUtil.GetDictByDictType("备件属类"));
}

或者,我们可以根据字典的类型,来进一步做一个扩展函数,来简化绑定的处理。

/// <summary>
///绑定下拉列表控件为指定的数据字典列表/// </summary>
/// <param name="control">下拉列表控件</param>
/// <param name="dictTypeName">数据字典类型名称</param>
/// <param name="defaultValue">控件默认值</param>
/// <param name="emptyFlag">是否添加空行</param>
public static void BindDictItems(this ComboBoxEdit control, string dictTypeName, string defaultValue, bool emptyFlag = true)
{
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]));
}

control.BindDictItems(itemList, defaultValue, emptyFlag);
}

使用了这些简化的扩展函数,我们可以对系统的字典,根据字典类型来进行绑定了。

private voidInitDictItem()
{
this.txtManufacture.BindDictItems("供货商");this.txtSearchManufacture.BindDictItems("供货商");this.txtSearchDept.BindDictItems("部门");
}

如上代码所示,简化了很多处理,就一个函数就可以绑定了系统字典类型的下拉列表了。

2、带序号的GridLookUpEdit下拉列表

有时候,我们在一些常见的系统里面,经常看到一些带有序号的下拉列表,其实里面就是一个GridControl的控件,我们只需要赋值对应的列表数据源,以及设置显示的列内容,并重写下序号的展示处理就可以实现了。

我们先来看看实际的效果。

上面的列表是一个GridControl的内在控件,我们使用这个 GridLookUpEdit下拉列表控件的时候,设置好GridControl的数据源和显示的列就基本上可以了。

//绑定数据源和显示的内容、隐藏值
this.txtProjectList.Properties.DataSource =list;this.txtProjectList.Properties.ValueMember = "Value";this.txtProjectList.Properties.DisplayMember = "Text";//设置Grid显示的列信息
var columns = new List<LookUpColumnInfo>()
{
new LookUpColumnInfo("Value", "显示名称")
};
for (int i = 0; i < columns.Count; i++)
{
this.txtProjectList.Properties.View.CreateColumn(columns[i].FieldName, columns[i].Caption,
columns[i].Width,
true, UnboundColumnType.Bound, DefaultBoolean.False, FixedStyle.None);
}
//其他属性设置 this.txtProjectList.Properties.ImmediatePopup = true;this.txtProjectList.Properties.TextEditStyle =TextEditStyles.Standard;this.txtProjectList.Properties.PopupWidthMode =DevExpress.XtraEditors.PopupWidthMode.ContentWidth;//设定列表的序号的宽度和显示文本 this.txtProjectList.Properties.View.IndicatorWidth = 40;this.txtProjectList.Properties.View.CustomDrawRowIndicator += (s, e) =>{if (e.Info.IsRowIndicator && e.RowHandle >= 0)
{
e.Info.DisplayText
= (e.RowHandle + 1).ToString();
}
};

那么如果我们需要使用扩展函数来简化实际的代码,那么应该如何封装这个GridLookupEdit的下列控件呢,我们编写的扩展函数代码如下所示。

        /// <summary>
        ///绑定控件的数据源/// </summary>
        /// <param name="lookup">控件对象</param>
        /// <param name="dataSource">数据源</param>
        /// <param name="displayMember">显示字段</param>
        /// <param name="valueMember">值字段</param>
        /// <param name="showRowIndicator">是否显示序号</param>
        /// <param name="lookUpColumnInfos">显示的列</param>
        /// <returns></returns>
        public static object BindDictItems(this GridLookUpEdit lookup, object dataSource, string displayMember, string valueMember, bool showRowIndicator = true, paramsLookUpColumnInfo[] lookUpColumnInfos)
{
lookup.Properties.DataSource
=dataSource;
lookup.Properties.DisplayMember
=displayMember;
lookup.Properties.ValueMember
=valueMember;

lookup.Properties.View.Columns.Clear();
for (int i = 0; i < lookUpColumnInfos.Length; i++)
{
lookup.Properties.View.CreateColumn(lookUpColumnInfos[i].FieldName, lookUpColumnInfos[i].Caption,
lookUpColumnInfos[i].Width,
true, UnboundColumnType.Bound, DefaultBoolean.False, FixedStyle.None);
}
lookup.Properties.ImmediatePopup
= true;
lookup.Properties.TextEditStyle
=TextEditStyles.Standard;if(showRowIndicator)
{
lookup.Properties.View.IndicatorWidth
= 40;//重写序号显示,默认不显示数值 lookup.Properties.View.CustomDrawRowIndicator += (s, e) =>{if (e.Info.IsRowIndicator && e.RowHandle >= 0)
{
e.Info.DisplayText
= (e.RowHandle + 1).ToString();
}
};
}
returndataSource;
}

这样处理后,界面上简化了不少代码,如下使用代码所示。

var list = DictItemUtil.GetDictByDictType("备件类别");var columns = new List<LookUpColumnInfo>()
{
new LookUpColumnInfo("Value", "显示名称")
};
this.txtProjectList2.BindDictItems(list, "Text", "Value", true, columns.ToArray());

通过上面的两种方式,我们可以得到常见的两种下拉列表的数据绑定展示方式。

而对于其他类似的下拉列表,如树形列表,带搜索的SearchLookupEdit等控件,我们也可以用类似的方式编写自定义的扩展函数,这样我们使用起来就非常方便的了。类似我们下面的做法方式,分门别类的对它们进行一些简单的封装处理。

以上就是我们对于常见控件使用的一些微薄经验,希望大家有所借鉴。

刚完成一些前端项目的开发,腾出精力来总结一些前端开发的技术点,以及继续完善基于SqlSugar的开发框架循序渐进介绍的系列文章,本篇随笔主要介绍一下基于Vue3+TypeScript的全局对象的注入和使用。我们知道在Vue2中全局注入一个全局变量使用protoType的方式,很方便的就注入了,而Vue3则不能通过这种方式直接使用,而是显得复杂一些,不过全局变量的挂载有它的好处,因此我们在Vue3+TypeScript中也继续应用这种模式来处理一些常规的辅助类方法。

1、Vue2的全局挂载

Vue2的挂载由于它的便利性,常常会被大量的使用,只需要使用Vue.protoType.**的方式就可以注入一个变量到全局上,并在页面或者组件中,通过this.**就可以访问到,非常的方便,如下所示。

    Vue.prototype.getToken =getToken
Vue.prototype.guid
=guid
Vue.prototype.isEmpty
= isEmpty

如果注入一些函数定义,也是类似的方式

    //提示成功信息
    Vue.prototype.msgSuccess = function(msg) {this.$message({ showClose: true, message: msg, type: 'success'})
}
//提示警告信息 Vue.prototype.msgWarning = function(msg) {this.$message({ showClose: true, message: msg, type: 'warning'})
}
//提示错误信息 Vue.prototype.msgError = function(msg) {this.$message({ showClose: true, message: msg, type: 'error'})
}

有时候为了便利,会把一些常规的放在一个函数里面进行注册挂载处理。如下代码所示。

这样我们在main.js函数里,就可以直接导入并注册挂载即可。

//导入一些全局函数
import prototype from './prototype'Vue.use(prototype)

有了一些常规函数的挂载处理,我们可以在组件或者页面中,通过this引用就可以获得了。

2、Vue3+TypeScript的全局挂载

而相对于Vue2,Vue3由于语法的变化,全局挂载则不能通过这种方式进行处理了,甚至在页面或者组件中,都不能访问this指针引用了。

不过由于全局变量的挂载还是有它的好处,因此我们在Vue3+TypeScript中也继续应用这种模式来处理一些常规的辅助类方法。

网上的处理Vue3+TypeScirpt的挂载方式的介绍也比较多,如下是它的处理方式。创建一个独立的ts文件
useCurrentInstance.ts
,如下代码所示。

import { ComponentInternalInstance, getCurrentInstance } from 'vue'

//获取默认的全局自定义属性
export default functionuseCurrentInstance() {
const { appContext }
=getCurrentInstance() as ComponentInternalInstance
const proxy
=appContext.config.globalPropertiesreturn{
proxy
}
}

然后在页面代码中import进来实例化使用即可,如下代码所示。

<scriptsetup lang="ts">import useCurrentInstance from'/@/utils/useCurrentInstance' //使用常规方式获取对象
const { proxy }=useCurrentInstance();
let $u
=proxy.$u as $u_interface;//挂载的时候初始化数据 onMounted(async ()=>{
console.log($u.success(
'abcdefa'));
console.log($u.util.guid());
});

为了方便,我对上面的代码进行了加工处理,以便在使用的时候,尽可能的简单化一些。

在原先独立的ts文件
useCurrentInstance.ts
,文件上,设置代码如下所示。

import { ComponentInternalInstance, getCurrentInstance } from 'vue'

//直接获得注入的对应的$u实例
export function$user() {
const { appContext }
=getCurrentInstance() as ComponentInternalInstance
const proxy
=appContext.config.globalProperties;
const $u
=proxy.$u as $u_interface;return$u;
}

这样这个文件返回的$user函数就是一个我们挂载的用户自定义对象了,我们把它统一调用即可。

import { $user } from '/@/utils/useCurrentInstance' //使用简便模式获取注入全局变量$u
let $u = $user();//实例化

两行代码就可以直接获得一个当前app的全局对象的引用了。

由于我们封装了一些常规的方法和对象,我们在Vue3的setup代码中直接使用全局对象的$u的代码如下所示。

//显示编辑对话框
functionshowEdit(id) {if($u.test.isEmpty(id)) {
$u.warn(
"请选择编辑的记录!");return;
}
emit(
'showEdit', id)
}

至于全局对象中如何挂载自己的快捷对象,则是根据自己的实际需要了。

3、Vue3+TypeScript的全局挂载的对象接口定义

在前面我们大概介绍了全局对象的一些挂载和使用的过程,以及相关的实例代码,不过我们可能一些刚使用Vue3+TypeScript的朋友可能不太清楚typescript中强类型的一些约束,它能给我们带来很多语义提示的好处的。

我们知道,基于Typescrip的项目,在项目中都有一个typescript的配置文件tsconfig.json,其中对一些typescript的目录或者设置进行设定。由于在VSCode加载项目的时候,会把项目相关的类型定义加载进来,它的配置在Include的配置项中。

其中一些项目全局通用的定义放在了types/*.d.ts 里面的,我们查看types目录,可以看到很多全局的定义信息,如下所示。

我们在这里增加一个独立的文件,来设置我们定义挂载对象的接口类型信息。

另外我们打算的全局辅助类对象的信息,放在util目录里面,如下所示。

编写相关的代码,并提供一个install的组件安装方法,给在main.ts中调用处理。

在mian.ts中,使用use的方式实现挂载处理即可。

 app.use($u);//挂载自定义的一些变量辅助类

而对应的辅助类接口定义,统一放在全局的Types目录的一个单独的$u.d.ts文件中定义。

例如我们定义常规弹出消息的函数接口如下所示。

//定义自定义类$u的接口类型
interface message_interface {
Message(message: string): any;
success(message: string): any;
warn(message: string): any;
error(message: string): any;
confirm(message
= '您确认删除选定的记录吗?'): Promise<any>;
};

其他的一些接口定义,则进行组合处理即可。

而我们最终的目的就是通过
$u
可以获得相关
$u_interface
的接口信息即可。

最后我们来看看用户信息管理页面的界面效果,页面很多地方使用了基于Vue3+TypeScript的全局对象的注入处理。如一些信息提示,一些通用函数的调用等。

系列文章:


基于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端界面

在前面随笔《
基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理
》中我们已经介绍过,对于相关的业务表的界面代码,我们已经尽可能把不同的业务逻辑封装在不同的页面组件中,隔离变化的差异,因此界面组件化后,就可以利用代码生成工具进行统一的界面代码的生成了,而且由于变化的隔离处理,我们实际上维护的代码变得更加方便维护了。本篇随笔介绍在整合代码生成工具进行前端界面的生成的一些思路和实际的界面代码的生成。

1、页面的模块化处理

在前面随笔《
基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理
》中我们已经介绍过,常规页面包含有列表界面,新增、编辑、查看、导入等界面,除了列表页面,其他内容以弹出层对话框的方式进行处理,如下界面示意图所示。

根据以上的页面划分,我们把一个页面分为search.vue、edit.vue、import.vue、view.vue、index.vue,其中index.vue为整合各个组件的主页面,在视图中如下所示。我们每个业务模块都是如此统一划分,因此比较统一,同时也是为后续的代码生成工具批量生成做好准备。

因此在index.vue页面中,我们整合了几个组件页面即可,如下所示。

<template>
  <divclass="main">
    <!--条件及列表展示-->
    <Searchref="searchRef"@show-import="showImport"@show-add="showAdd"@show-view="showView"@show-edit="showEdit" />

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

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

import Search from
'./search.vue';
import ViewData from
'./view.vue';
import EditData from
'./edit.vue';
import ImportData from
'./import.vue';

1)查看视图页面

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

<template>
  <el-dialogv-if="isVisible"v-model="isVisible"title="查看信息"append-to-body @close="closeDialog">
    <el-formref="viewRef":model="viewForm"label-width="100px">
      <el-tabstype="border-card">
        <el-tab-panelabel="基本信息">
          <el-descriptionstitle="":column="2"border>
            <el-descriptions-itemlabel="显示名称">{{ viewForm.name }}</el-descriptions-item>
            <el-descriptions-itemlabel="Web地址">{{ viewForm.url }}</el-descriptions-item>
            <el-descriptions-itemlabel="Web图标">
              <!--{{ viewForm.webIcon }}-->
              <icon:icon="viewForm.webIcon" />
            </el-descriptions-item>
            <el-descriptions-itemlabel="排序">{{ viewForm.seq }}</el-descriptions-item>
            <el-descriptions-itemlabel="可见">
              <el-tagv-if="viewForm.visible"type="success"effect="dark">可见</el-tag>
              <el-tagv-else type="danger"effect="dark">隐藏</el-tag>
            </el-descriptions-item>
            <el-descriptions-itemlabel="展开">
              <el-tagv-if="viewForm.expand"type="success"effect="dark">展开</el-tag>
              <el-tagv-else type=""effect="dark">收缩</el-tag>
            </el-descriptions-item>
            <el-descriptions-itemlabel="创建时间">
              <el-date-pickerv-model="viewForm.createTime"align="right"type="datetime"placeholder="选择日期"value-format="YYYY-MM-DD HH:mm"disabled/>
            </el-descriptions-item>
            <el-descriptions-itemlabel="特殊标签">{{ viewForm.tag }}</el-descriptions-item>
          </el-descriptions>
        </el-tab-pane>
      </el-tabs>
    </el-form>
    <template#footer>
      <spanclass="dialog-footer">
        <el-button@click="closeDialog">关闭</el-button>
      </span>
    </template>
  </el-dialog>
</template>

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

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

然后我们在js代码中抛出show的方法,便于父组件的调用。

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

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

同时在页面里面,也定义一个表单的对象引用,便于上面模板组件的显示。

const viewRef = ref<FormInstance>(); //表单引用//表单属性定义
let viewForm =reactive({
iD:
'',
pid:
'',
name:
'',
icon:
'',
seq:
'',
functionId:
'',
visible:
0,
expand:
0,
winformType:
'',
url:
'',
webIcon:
'',creator:'',createTime:'',tag:'',
});

如果是查看详细的视图中有树形列表,我们还可以在onMounted的处理中,添加获取树列表的数据即可,如下代码所示。

//挂载的时候初始化数据
onMounted(() =>{//设置默认值
getTree();
});
//初始化树列表 let treedata =ref([]);
const getTree
= () =>{//树列表数据获取 menu.GetAll().then((data) =>{
treedata.value
= []; //树列表清空 var list = data?.items;if(list) {var newTreedata =$u.util.getJsonTree(list, {
id:
'id',
pid:
'pid',
label:
'name',
children:
'children',
});
treedata.value
=newTreedata;
}
});
};

而查看视图的触发,往往在列表的操作按钮中,或者双击表格行进行触发,界面如下所示。

界面如下代码所示。

而调用查看详细页面的事件,传递对应的id并调用组件实例抛出的方法即可。如下代码所示。

//显示查看对话框处理
const viewRef = ref<InstanceType<typeof ViewData>>();functionshowView(id) {
viewRef.value.show(id);
}

至此,一个独立的视图页面组件,以及如何触发调用就完成了,视图页面单独维护,便于代码的管理,同时也隔离了复杂的页面逻辑。

视图页面效果如下所示。

2)新增编辑视图页面

在常规的处理中,往往编辑和新增的界面是差不多的,差异不同的地方,我们可以通过条件 if 的方式进行处理即可。因此可以把两者放在一个组件中实现对话框内容和逻辑处理。

刚才我们提到了Index.vue页面,是对几个组件的统筹处理,如下代码所示。

<template>
  <divclass="main">
    <!--条件及列表展示-->
    <Searchref="searchRef"@show-import="showImport"@show-add="showAdd"@show-view="showView"@show-edit="showEdit" />

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

从上面代码我们看到,在HTML代码中,我们引入对应的组件,并在主查询页面中触发事件即可,如下所示。

其中showAdd和ShowEdit类似,都是调用编辑/新增的对话框,不同的是,通过传递id来辨别是否为新增,如果需要传入pid的父节点信息,那么我们也可以创建一个showAdd的方法。

//新增、编辑表单引用
const editRef = ref<InstanceType<typeof EditData>>();//显示新增对话框
function showAdd(pid?: string |number) {
editRef.value.showAdd(pid);
}
//显示编辑对话框 functionshowEdit(id) {
editRef.value.show(id);
}
//新增/更新后刷新 functionrefreshData() {
searchRef.value.getlist();
}

编辑业务数据的对话框和查看详细的类似,不过这里是需要使用输入控件进行内容编辑修改处理的。

对于一些复杂控件,我们可以自定义组件来简化在界面上的使用,尽可能的快捷、简单。

我们在组件中定义showAdd和show的方法,便于父组件的调用即可,如果传递了id值,我们根据业务对象的get方法获取详细的数据,赋值到表单对象上就可以正常显示了。

//默认标题为[编辑信息],当show传入id为空的时候,为[新建信息]
let title = ref('编辑信息');
let isAdd
= ref(false); //是否新增状态 //显示窗口(编辑/新建) const showAdd = async (pid?: string | number) =>{
title.value
= '新建信息';
isAdd.value
= true;
resetFields();
//清空表单 editForm.pid= pid + '';
isVisible.value
= true; //显示对话框 };
const show
= async (id?: string | number) =>{if (!$u.test.isNullOrUnDef(id)) {
title.value
= '编辑信息';
isAdd.value
= false;
await menu.Get(id).then((data)
=>{ Object.assign(editForm, data);
});
}
else{
title.value
= '新建信息';
isAdd.value
= true;
resetFields();
//清空表单 } isVisible.value = true; //显示对话框 };

抛出这两个实例的方法,供外面调用。

//暴露组件属性和方法
defineExpose({
show,
showAdd,
});

为了承载表单的数据模型,我们创建相关的业务对象

const editRef = ref<FormInstance>(); //表单引用//表单属性定义(初始化)
let editForm =reactive({
id:
'',
pid:
'',
name:
'',
icon:
'',
functionId:
'',
winformType:
'',
url:
'',
seq:
'001',
isTop:
false,
expand:
1,
visible:
1,
webIcon:
'',
tag:
'web', //Web专用 });

保存数据的时候,我们判断表单的rules规则,如果符合通过,那么提交数据并提示用户即可。

//保存数据处理
async functionsubmitData() {var formEl =editRef.value;if (!formEl) return;

await formEl.validate(async (valid)
=>{if(valid) {//验证成功,执行下面方法 var result = false;if(isAdd.value) {
result
= await menu.Create(editForm); //新增保存 } else{
result
= await menu.Update(editForm); //编辑保存 }if(result) {
$u.success(
'操作成功!'); //提示信息 emit('submit'); //提示刷新数据 closeDialog(); //重置窗口状态 } else{
$u.error(
'操作失败');
}
}
});
}

编辑界面的效果如下所示。

3)导入界面的处理

导入界面,一般我们分为几个步骤,一个是提供导入模板下载,然后上传文件并显示数据,然后确认提交即可。

由于导入数据的逻辑上大致类似,不同的是他们的业务数据和验证规则,因此我们通过自定义组件的方式,来简化相关的处理。

我通过改造ele-import 的第三方组件,让它支持Vue3+Typescript语法,实现对业务数据的上传操作。

<template>
  <divclass="main">
    <!--条件及列表展示-->
    <Searchref="searchRef"@show-import="showImport"@show-add="showAdd"@show-view="showView"@show-edit="showEdit" />
    <import-dataref="importRef"@finish="finishImport" />
  </div>
</template>

而在import.vue页面里面,我们的代码是使用ele-import来处理即可。

<template>
  <div>
    <!--模板导入信息-->
    <ele-import:fields="importForm.fields":filepath="importForm.filepath":append="importForm.append":formatter="importForm.formatter":rules="importForm.rules":tips="importForm.tips":title="importForm.title":visible="isVisible":request-fn="saveImport"@close="close"@finish="finishImport" />
  </div>
</template>

通过传入对应的数据,如导入字段,以及规则,以及下载文件等相关参数,就可以实现了文件的上传处理了。

//请求服务端处理上传数据,返回一个Promise对象
const saveImport = async (data) =>{//console.log(data);
  const result =await menu.SaveImport(data);//console.log(result);
  if(result) {returnPromise.resolve();
}
else{returnPromise.reject();
}
};

同时,这也是一个弹出窗口,因此也需要暴露show方法给外部调用。

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

而这个组件的相关数据信息,定义在importForm中,我们来看看对应的数据格式。

const importForm =reactive({//Excel 模板导入数据
  //弹出层标题
  title: '功能菜单导入',//提示信息
  tips: [], //['商品编号 必填', '产品类型 必填', '商品名称 必填'],
  //字段名称参照表
fields: {//字段根据需要裁剪
    pid: '父ID',
name:
'显示名称',
icon:
'图标',
seq:
'排序',
url:
'Url地址',
webIcon:
'菜单图标',
systemType_ID:
'系统编号',
tag:
'特殊标签',
},
//附加数据, 在每条记录上都会加这两个字段和值 append: {//company: '广州爱奇迪', //leader: '伍华聪' },//参数校检, 和 element-ui 中 form表单中传递的rules一样, 都是使用的 async-validator 库 //https://element.eleme.cn/#/zh-CN/component/form#biao-dan-yan-zheng rules: {
pid: { type:
'string', required: true, message: '父ID必填'},
name: { type:
'string', required: true, message: '显示名称必填'},
url: { type:
'string', required: true, message: 'Url地址必填'},
webIcon: { type:
'string', required: true, message: '菜单图标必填'},
systemType_ID: { type:
'string', required: true, message: '系统编号必填'},
tag: { type:
'string', required: true, message: '特殊标签必填'},
},
//Excel模板下载地址。注意, 只能是.xlsx的文件, .xls或者.cvs都会报错 filepath: 'http://localhost:5043/UploadFiles/template/功能菜单.xlsx',
});

整个导入模块,会通过这个对象的数据格式,进行不同的显示和校验处理等操作。界面效果如下所示。

其中第一步提示信息,并提供模板文件下载

第二步提供文件上传处理。

第三步确认数据并完成处理。

这种统一的操作,我们通过封装,隔离了逻辑步骤的协调处理,只需要在业务组件中生成相关的数据即可,便于使用。

2、利用代码生成工具快速生成

通过上面的介绍,我们了解到整个页面的组件代码结构,因此可以利用它们和数据表之间的关系,生成对应的页面组件,利用代码生成工具Database2Sharp强大的数据库元数据和模板引擎,我们构建了对应的框架代码生成规则,因此统一生成即可,提高了代码开发的效能,同时也统一了代码的结构,便于大项目的维护。

对于SQLSugar的项目框架,我们为了方便,分别单独提供后端代码和Web API代码的生成、Winform界面代码的生成,以及前面介绍到的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端界面

在一个项目中,客户要求对报表中的签名进行仿手写的签名处理,因此我们原先只是显示相关人员的姓名的地方,需要采用手写方式签名,我们的报表是利用FastReport处理的,在利用楷体处理的时候,开发展示倒是正常效果,不过实际上在服务器运行的时候,出来的确实正规的宋体格式,相应的字体都已经安装,不过还是没有生效。因此采用变通的方式,在对应签名的地方采用图片的格式显示,实际效果达到要求。本篇随笔介绍这个过程,利用FastReport传递图片参数,在报表上展示签名信息的处理。

1、报表上的设计处理

例如我们要在报表落款的附近记录相关人员的名字,因此采用签名的显示方式会比较合理。

因此设计相关的报表,本来想是采用文本的方式,变化字体的方式来快捷实现的

不过在报表是在服务器上生成图片的方式,导致正常出来的图片,却没有变化字体,导致出来的是正常的宋体格式。

因此考虑使用图片的格式方式来处理。在其中放置Picture控件,如下所示。

调整好Picture控件的高度和宽度,让它在设计的空白上合适的展示即可。

为了实现图片格式的显示,我们需要在报表的图片控件的BeforePrint事件中解析数据(来自传递参数),数据格式为Base64字符串(从Byte数据转换),如下代码所示。

namespaceFastReport
{
public classReportScript
{
private void shopDoctorImg_BeforePrint(objectsender, EventArgs e)
{
var img = GetImage("ShopDoctor");if(img != null){
shopDoctorImg.Image
=img;
}
}
private void tiaopeiImg_BeforePrint(objectsender, EventArgs e)
{
var img = GetImage("Tiaopei");if(img != null){
tiaopeiImg.Image
=img;
}
}
private void CheckDoctorImg_BeforePrint(objectsender, EventArgs e)
{
var img = GetImage("CheckDoctor");if(img != null){
CheckDoctorImg.Image
=img;
}
}
private void CheckPharmacistImg_BeforePrint(objectsender, EventArgs e)
{
var img = GetImage("CheckPharmacist");if(img != null){
CheckPharmacistImg.Image
=img;
}
}
private Image GetImage(stringsignName)
{
Image img
= null;string imgStr = (string)Report.GetParameterValue(signName);if(!string.IsNullOrEmpty(imgStr))
{
byte[] imgData=Convert.FromBase64String(imgStr);using(MemoryStream ms = newMemoryStream(imgData))
{
img
=System.Drawing.Image.FromStream(ms);
}
}
returnimg;
}

}
}

其中主要注意的是,我们传递的图片数据需要采用Base64String的格式才能正常传递和展示。

2、报表传递图片数据

完成了报表的设计处理,我们剩下的就是在实际的报表中传递对应的参数数据了。

我们把签名图片,放在相对的目录上,如下所示。

然后编写一个公用的读取图片为Base64String的函数处理,如下所示。

        //通过姓名获取签名图片的Base64
        private string GetSignImage(stringsignName)
{
var result = "";string imagePath = Path.Combine(baseDir, $"Report/signs/{signName}.png");if(File.Exists(imagePath))
{
var stream =FileUtil.FileToStream(imagePath);var image =FileUtil.StreamToBytes(stream);if (image != null)
{
result
=Convert.ToBase64String(image);
}
}
returnresult;
}

接着就是根据对应的报表进行加载,并设置相关的参数进行传递给报表即可,如下测试代码所示。

    //生成PDF报表文档到具体文件
    Report report = newReport();
report.Load(reportFile);
//定义参数和数据格式 var dict = new Dictionary<string, object>();#region 测试数据源dict.Add("Name", "张三");
dict.Add(
"Gender", "");
dict.Add(
"Age", 32);
dict.Add(
"Telephone", "18620292076");
dict.Add(
"CreateTime", "2019-10-13 22:30:15");
dict.Add(
"CheckDoctor", GetSignImage("张医生"));//"张医生" dict.Add("CheckPharmacist", GetSignImage("张医生")); //"李药师" dict.Add("SendUser", "王小姐");
dict.Add(
"QrCode", "http://www.iqidi.com");
dict.Add(
"BarCode", "1234567890");//图片文件 dict.Add("ShopDoctor", GetSignImage("张医生"));
dict.Add(
"Tiaopei", GetSignImage("张医生"));
dict.Add(
"Fayao", GetSignImage("王小姐"));#endregionreport.RegisterData(dt,"Detail");foreach (string key indict.Keys)
{
report.SetParameterValue(key, dict[key]);
}
//运行报表 report.Prepare();

由于我们的报表,最终是生成PDF或者图片的方式,方便客户进行在线查询的,因此可以选择PDF或者图片的格式生成。

    //运行报表
report.Prepare();//导出PDF报表//PDFExport export = new PDFExport();//多个图片导出
    int count = 1;string firstFileName = exportImgPath.Replace(".png", "");foreach (PageBase item inreport.Pages)
{
string fileName = string.Format("{0}_{1}.png", firstFileName, count);
exportImgPath
=fileName;//Resolution= 300可以提高分辨率 report.Export(new ImageExport() { PageRange = PageRange.Current, CurPage = count, Resolution= 300}, fileName);
count
++;
}

最后生成的图片格式如下所示,顺利吧签名的图片贴在对应的单元格中即可。