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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

系列文章:


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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

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

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

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

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

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

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

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

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

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

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

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

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

2、data属性定义

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4、组件prop属性定义

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5、Emits事件声明

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

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

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

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

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

或者直接整合一起声明。

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

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

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

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

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

6、
Computed计算函数的使用

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

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

7、Watch函数的使用

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

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

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

8、onMounted函数的使用

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

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

或者组件里面

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

9、自定义组件的ModelValue

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

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

或者日期组件

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

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

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

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

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

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

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

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

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

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

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

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

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

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

npm i -D unplugin-auto-import

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

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

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

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

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

系列文章:


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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

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

1、页面模块组件的划分

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

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

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

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

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

2、页面组件的开发

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

viewRef.value.show(id);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

系列文章:


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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

在我们实际项目开发过程中,往往需要根据实际情况,对组件进行封装,以更简便的在界面代码中使用,在实际的前端应用中,适当的组件封装,可以减少很多重复的界面代码,并且能够非常简便的使用,本篇随笔介绍基于ElementPlus的上传组件进行封装。

1、El-Upload上传组件的使用场景及数据库设计

在官网地址
https://element-plus.org/zh-CN/component/upload.html
上有关于该组件的详细使用代码案例。

大概有个场景我需要根据需要展示文件的,一个是文件展示方式(非图片格式)。

一种是肖像方式处理。

一种方式是图片缩略图列表方式。

还有就是支持拖动方式上传。

我目前大概就是想到应用这几种方式,细节的地方可以根据需要进行微调即可。

一般来说,我们附件信息是单独存储在一个表里面的,附件则是存储在相应的文件系统或者FTP目录中。如果需要了解后端不同方式的文件上传方式,可以了解随笔《
基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传

而实际我们业务表里面,只需要存在一个字符串字段,它是一串GUID的值,用于引用单个或者多个附件列表的AttahmentGUID值即可。

在前端界面使用的的时候,我则希望自定义的组件使用的时候,尽量简单,因此对于附件上传的URL地址、Upload组件的相关设置,应该屏蔽在自定义组件即可,组件只需要提供一个v-model的绑定值和一些需要的属性即可。

2、文件上传后端处理

在介绍前端上传组件自定义前,我们需要提供一个上传文件的URL地址给前端,接收前端的文件流,以及相关FormData里面的参数值。

后端上传处理的函数定义如下所示

        /// <summary>
        ///多文件上传处理(自动根据配置文件选择合适的上传方式)/// </summary>
        /// <param name="guid">附件组GUID</param>
        /// <param name="folder">指定的上传目录</param>
        /// <returns></returns>
        [RequestSizeLimit(100000000)] //请求正文最大大小100M
[HttpPost]
[Route(
"postupload")]public async Task<List<ResponseFileInfo>> PostUpload()

其中 ResponseFileInfo 是我们定义一个返回给前端使用的对象,记录文件的名称、URL,id信息。

    /// <summary>
    ///上传后文件返回的结果信息/// </summary>
    public classResponseFileInfo
{
/// <summary> ///默认构造函数/// </summary> publicResponseFileInfo() { }/// <summary> ///参数化构造函数/// </summary> /// <param name="id"></param> /// <param name="name"></param> /// <param name="url"></param> public ResponseFileInfo(string id, string name, stringurl)
{
this.Id =id;this.Name =name;this.Url =url;
}
/// <summary> ///记录ID/// </summary> public string Id { get; set; }/// <summary> ///文件名称/// </summary> public string Name { get; set; }/// <summary> ///文件路径/// </summary> public string Url { get; set; }
}

在后端上传处理中,我们可以通过HTTP上下文获得传过来的对应参数和文件列表信息,如下代码所示。

        public async Task<List<ResponseFileInfo>>PostUpload()
{
var httpContext = this.HttpContext;string guid = httpContext.Request.Form["guid"];//input.guid; string folder = httpContext.Request.Form["folder"];//input.folder; var files = httpContext.Request.Form.Files;

最后根据文件信息和相关参数,构建附件数据库信息,以及存储文件就可以了,如下代码所示。

3、文件上传组件的前端封装和处理

对于编辑状态下的前端处理,我们只需要绑定v-model的值,以及指定的一些参数就可以了。

如果是禁止上传的详细展示,可以设置disabled属性为true就可以了

<el-form-itemlabel="资料文档">
     <my-uploadv-model="viewForm.attachGUID":data="{ guid: viewForm.attachGUID, folder: '用户图片' }"disabled/>
 </el-form-item>

用户界面展示如下所示。

通过简单的封装,我们就可以在极少代码的基础上定制自己的功能界面了。易于阅读和维护,同时也有助于我们后面通过代码生成工具的方式快速生成相关的代码。

自定义组件,我们根据需要定义一些属性,并对它设置默认值,如下所示代码。

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

import { ElMessage, ElMessageBox } from
"element-plus";
import type { UploadInstance, UploadProps, UploadUserFile } from
"element-plus";
import { Plus, UploadFilled } from
"@element-plus/icons-vue";
import { isNullOrUnDef, isEmpty } from
"/@/utils/is";
import fileupload from
"/@/api/fileupload";

defineOptions({ name:
"MyUpload"});//声明Props的接口类型 interface Props {
modelValue
?: string;//接受外部v-model传入的值,记录的AttachGUID值 data?: Record<string, any>;//上传时附带的额外参数: {a:b} headers?: Headers|Record<string, any>;//设置上传的请求头部 listType?:"text" | "picture" | "picture-card";//文件列表的类型 disabled?:boolean;//是否禁用 multiple?:boolean;//是否支持多选文件 limit?: number;//最大允许上传个数 fileSize?: number;//大小限制(MB) fileType?: Array<string>;//文件类型, 例如['png', 'jpg', 'jpeg'] isShowTip?:boolean;//是否显示提示 showFileList?:boolean;//是否显示文件列表 withCredentials?:boolean;//支持发送 cookie 凭证信息 isAvatarUpload?:boolean;//是否是头像上传 drag?:boolean;//是否启用拖拽上传 buttonText?: string;//按钮文本 }//使用默认值定义Props const props=withDefaults(defineProps<Props>(), {
modelValue:
"",//对应自定义组件的v-model的值 data: ()=>{return null;
},
headers: ()
=>{//用于上传文件的身份认证 return{ Authorization:"Bearer" +getAccessToken() };
},
listType:
"text",//"text" | "picture" | "picture-card" disabled:false,
multiple:
false,//limit: 10, fileSize:2,//MB fileType: ()=>{return["png","jpg","jpeg","gif"];
},
isShowTip:
true,
showFileList:
true,
withCredentials:
true,//支持发送 cookie 凭证信息 isAvatarUpload:false,//头像上传方式 drag:false,//是否启用拖拽上传 buttonText:"单击上传",

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

在我们展示组件的时候,我们根据v-model绑定的modelValue值进行加载附件列表或者图片信息,

//挂载的时候初始化数据
onMounted(async () =>{if (!isEmpty(props.modelValue)) {//使用字典类型,从服务器请求数据
    await fileupload.GetByAttachGUID(props.modelValue).then(data =>{//console.log(data);
      if (data.length > 0) {var list: Array<UploadUserFile> =[];
data.map(item
=>{
let url
=getUrl(item);
let { id, fileName, fileExtend }
= item; //结构对象属性 list.push({
name: fileName,
url: url,
uid: id
});
});

fileList.value
=list;//如果是头像上传模式,显示最后一个图片 if(props.isAvatarUpload) {
avatarImageUrl.value
= list[0]?.url;
}
}
});
}
});

在预览文件的时候,我们判断,如果是图片文件,就打开窗口展示图片,否则就下载附件即可。

//预览文件
const handlePreview: UploadProps["onPreview"] = uploadFile =>{//console.log(uploadFile);
  //当格式为图片就预览图片,否则下载文件
  let filename =uploadFile.name;
let fileurl
=uploadFile.url;
let fileExtension
= "";//校检文件类型 var imageTypes = ["png", "jpg", "jpeg", "gif"];if (filename.lastIndexOf(".") > -1) {
fileExtension
= filename.slice(filename.lastIndexOf(".") + 1);
}
const isTypeOk
= imageTypes.some(type =>{if (fileExtension && fileExtension.indexOf(type) > -1) return true;return false;
});
if(isTypeOk) {//预览图片 dialogImageUrl.value =fileurl;
dialogTitle.value
=filename;
dialogVisible.value
= true;
}
else{//下载文件 dialogVisible.value = false;//openWindow(fileurl, { target: "_self" }); window.open(fileurl, "_self");
}
};

移除文件的时候,我们根据文件的id,在数据库后端进行删除附件和附件信息。

//移除文件前操作
const beforeRemove: UploadProps["beforeRemove"] = (uploadFile, uploadFiles) =>{//console.log(uploadFile);
  //${uploadFile.name}
  return ElMessageBox.confirm(`确认删除该文件吗?`).then(
async ()
=>{var result = false;var id =uploadFile.uid;if (!isEmpty(id)) {
result
=await fileupload.Delete(id);
}
returnresult;
},
()
=> false);
};

系列文章:


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

在我们开发开发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);
}
}
}

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