2023年2月

在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
++;
}

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

工作流是集成系统的模块应用,使用权限管理系统的身份认证登录后即可使用。本篇随笔介绍工作流模块的界面功能效果。

1、我的审批工作

我的审批工作,包括【我发起的】、【我的待办】、【我的已办】几类大项,并可以根据查询条件进行过滤列表,界面如下所示:

双击申请单,可以打开查看详细的申请单信息,如下界面所示。

如果审批单可以进行审批处理,则会出现相关的审批按钮,单击【审批】按钮,弹出审批对话框,可以录入相关的审批意见,如下界面所示。

如果表单需要撤销,单击【撤销】按钮,会弹出申请单撤销的确认界面,在其中录入撤销意见确认即可。

如果需了解申请单的流程日志信息,单击【流程日志】按钮,弹出申请单的相关日志信息,如下界面所示。

2、业务受理列表

业务受理列表展示当前工作流模块中可以创建的申请表单入口,如下界面所示。

我们可以根据左侧树列表的业务分类进行过滤,展示不同分类的申请单。单击其中图形按钮,可以创建一个新的申请单,申请单界面如下所示。

有些是简单的单表信息,有些则是主从表的明细表单,不同的表单界面,需要在开发模式进行开发处理。

3、我的草稿

我的草稿是保存用户尚未完成的申请单,在用户编辑申请单的时候,可以暂存在草稿里面,方便下次继续完成填写工作。我的草稿列表界面如下所示。

双击草稿列表的申请单,可以打开申请单的填写界面,如下所示。

4、已办业务查询

已办业务查询,按照不同的业务分类,展示相关申请单的列表,列表界面如下所示。

双击列表记录,可以查看具体的申请单详细信息。

5、流程模板

前面介绍过的工作流的业务受理列表,展示的是可以创建申请单的图形按钮列表,如下界面所示的效果列表。

其中的数据就是在这个流程模板中配置管理的,流程模板中管理工作流业务中可以使用的申请单配置信息,流程模板界面如下所示。

其中详细的配置窗体界面如下所示,这里配置框架处理中所需的相关参数数据,以及配置的流程节点。

单击【流程设置】,可以设置该申请单模板的流程步骤,如下界面所示。

6、流程环节管理

流程环节管理,是定义系统工作流中用到的流程环节类型(处理类型)信息,如下界面所示。

其中有常规的审批、归档、会签、阅办等操作,工作流需要根据这些流程类型来显示不同的处理界面。双击记录,可以进行编辑修改处理。

7、所有申请单

所有申请单列出工作流系统中的所有流程表单的信息,便于管理员针对性的查找申请单信息进行调整处理。

双击申请单列表,可以打开具体的申请单进行查看详细信息,如下所示。

8、流程审批操作

对于一个流程处理操作,我们知道一般有审批通过、拒绝、退回到某步骤、转发到内部阅读、阅读,以及包括起草者能撤销表单呢等操作,当然如果还有一些具体的业务,可能还会有一些流程的处理才操作,不过基本上也可以归结为上面几种,只是他们每步处理的数据内容不同而已。因此审批的操作步骤分类如下所示。

这些操作我们都可以通过一些界面操作的封装实现,因为他们基本上都是通用的,我们传入一些流程
ID等相关标识后,就能交给这些标准的操作界面完成了。

如审批界面如下所示,里面包含了通过、拒绝,跳回到某步骤,增加步骤等。

上面的界面是审批过程中,对于某一个流程处理人员实现的操作,而有时候,我们可能需要针对多个人进行某个步骤的处理,如会签处理,会签处理处理界面效果如下所示。

以及传递给内部人员进行分阅操作,那么就应该选定多个人员进行处理,大概的处理界面效果如下所示。

当然,若申请人的申请单填写错误,需要撤销的话,那么也应该有这个操作,撤销表单后,就可以重新填写表单,然后再次提交进行流程。

9、流程会签处理

会签是指创建一个或多个子流程供相关人员进行审批,等待全部人员完成处理后再次回到主流程上,然后决定是否继续流转到下一个流程步骤上去,一般的申请单的主流程如下所示。

这里设置的会签处理就是其中一个步骤,一旦会签处理步骤发起会签,就会构建多个可供审批的子流程了,如下所示。

在会签发起的步骤,指定参与具体流程会签审批的人员,然后流程则会流转到不同人员进行相关的处理【待办事项】。

我在工作流中定义会签完成后,由会签发起人审核(会签结果审核),决定是否进入下一步流程,在审核过程中决定如何处理这个申请单。

在流程定义里面,我们创建一个会签的流程步骤,我们以请假单为例,加入我们要求请假需要由各组长会签通过,然后在继续下面的部门审批、总经理审批步骤,如下所示。

增加会签后的流程步骤如下所示。

完成后可以在流程步骤列表中看到会签的步骤了,如下所示。

我们创建一个请假申请单,用来发起会签处理,介绍会签的步骤说明。

完成请假单后提交给相关处理人,处理人员在待办事项中查看申请单,如下界面所示。

那么他会发起【发起会签】的处理操作,把相关的投票权发送给各个组长进行会签处理。

会签发起后,各个参与会签的人员在【待办事项】里面处理会签意见,如下所示。

各个待审批的人员进行处理后,最后返回给会签发起人决定是否进入下一步流程,如下所示。

系列文章:


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