0


分享项目 - Vue3 + TS + element-ui-plus 项目 -- Table表格表单

文章目录

前言

今天看一个 ts 项目的 table 模块,亲身体验这是公司后台管理系统一定会使用到的,也是最常使用到的,这个项目对新手很友好,毕竟是一个相对来说比较空的项目模板,对于我来说就是一个学习的记录,一些技术的分享,手把手告知新手别人的代码怎么读,甚至还能帮该开源项目作者获取一些热度,我很乐于做这样的事情(已经争得原作者许可,感谢 🤓)项目地址:V3 Admin Vite

通过该文章可以学习到 :

  • element-ui-plus 的表单、表格等组件的使用
  • 怎么阅读他人的代码、怎么写出优雅炫酷的代码
  • api 请求以及 api 请求拦截器、api 全局请求封装等知识点

在这里插入图片描述

项目地址以及怎么阅读别人的代码

我们来看一下具体代码是怎么实现的,我读别人的代码喜欢先看一下大体目录结构、然后从页面功能入手,然后在 html 中找到该组件,然后查看该组件使用的方法等,一直相连关联到底层封装代码(或者直接看脚本逻辑,从脚本逻辑入手,看大家习惯)

功能、底层封装、页面结构等等知道了,自然而然就通了

我一步一步就标注了我对该代码的思考,希望对于大家有所帮助

整体代码

<scriptlang="ts"setup>import{ reactive, ref, watch }from"vue"import{ createTableDataApi, deleteTableDataApi, updateTableDataApi, getTableDataApi }from"@/api/table"import{ type FormInstance, type FormRules, ElMessage, ElMessageBox }from"element-plus"import{ Search, Refresh, CirclePlus, Delete, Download, RefreshRight }from"@element-plus/icons-vue"import{ usePagination }from"@/hooks/usePagination"// 加载状态,这也是 element-ui-plus 的一种加载方法,可以查看 html 元素并访问 element-ui-plus 官网来找到该变量有什么用处// 定义 loading 为响应式状态值,ts 限制为布尔类型const loading = ref<boolean>(false)// 自己封装的页面功能,可以转到 对应 src目录下 hooks 文件夹中的 usePagination 中查看对应方法的功能// 相应代码在本文章下面,可对应查看或者直接去 GitHub 下载宝藏博主的源码码进行查看const{ paginationData, handleCurrentChange, handleSizeChange }=usePagination()//#region 增// 我们在 html 代码中可以看到 是使用在 el-dialog 组件的 v-module 属性上的,我们可以查看 element-ui-plus 文档查看该功能// v-model 控制这该组件是否显示const dialogVisible = ref<boolean>(false)// 表单对象实例 ts限制为表单实例 或 null// 在新增用户判断时需要使用到,被绑定在 表单的 ref 值上const formRef = ref<FormInstance |null>(null)// 表单输入值const formData =reactive({username:"",password:""})// 定义表单验证规则并使用 ts 进行类型规范constformRules: FormRules =reactive({username:[{required:true,trigger:"blur",message:"请输入用户名"}],password:[{required:true,trigger:"blur",message:"请输入密码"}]})// 创建新用户/修改用户 方法consthandleCreate=()=>{// 判断表单实例是否存在// 因为 validate 是 element-ui-plus 表单上的一个方法,所以需要使用到表单实例才可以使用该方法,现在我们知道了为什么要获取表单实例了// validate 接收一个回调函数,或返回 Promise,执行之前是有一个前提的,需要表单实例是存在的
  formRef.value?.validate((valid: boolean)=>{if(valid){// 如果 valid 存在,那么判断 currentUpdateId 是否为 undefined// currentUpdateId 是否有值决定着用户操作的是新增还是修改if(currentUpdateId.value ===undefined){// 发起创建 table 请求,携带用户名与用户密码createTableDataApi({username: formData.username,password: formData.password
        }).then(()=>{// 数据请求成功之后弹出提示信息
          ElMessage.success("新增成功")// 并将弹框设置为不显示
          dialogVisible.value =false// 这里的方法在下面 但是从命名就不难看出 这是新增成功之后重新请求一下所有的数据 保证页面数据的最新getTableData()})}else{// 前面也有提到 这里是一个炫酷写法,将新增和修改放在一个方法中,执行哪个方法取决于 currentUpdateId 是否有值// 不得不佩服作者代码写得很棒updateTableDataApi({id: currentUpdateId.value,username: formData.username
        }).then(()=>{
          ElMessage.success("修改成功")
          dialogVisible.value =falsegetTableData()})}// 没有 valid 值,将会退出该方法不执行任何操作}else{returnfalse}})}// 读到这里就知道 currentUpdateId 是一个关于什么的值了// 我们查到找 html 代码发现当弹窗关闭的时候会触发该方法// 捋一下思路,也就是弹窗关闭,currentUpdateId 值会清空(用户信息也会清空)// 所以我们可以知道用户信息是为了下次打开弹框不会发生之前数据还显示出来的状况// 而 currentUpdateId 则是当前更新 ID ,该值为 undefined 需要执行的是新增,如果当前拥有用户id,那么执行的就是更新constresetForm=()=>{
  currentUpdateId.value =undefined
  formData.username =""
  formData.password =""}//#endregion//#region 删// row 是当前点击列表项的数据consthandleDelete=(row: any)=>{
  ElMessageBox.confirm(`正在删除用户:${row.username},确认删除?`,"提示",{confirmButtonText:"确定",cancelButtonText:"取消",type:"warning"}).then(()=>{// deleteTableDataApi 是封装好的 request(ajax) 方法// 我们可以看看作者是怎么封装数据请求的deleteTableDataApi(row.id).then(()=>{
      ElMessage.success("删除成功")getTableData()})})}//#endregion//#region 改// 这里的修改方法只是给到了用户数据以及 当前ID 值,当用户点击确认按钮的时候才会发出真在的数据请求,将该值给到服务端处理// 所以这里只是一些简单的数据修改const currentUpdateId = ref<undefined| string>(undefined)consthandleUpdate=(row: any)=>{
  currentUpdateId.value = row.id
  formData.username = row.username
  formData.password = row.password
  dialogVisible.value =true}//#endregion//#region 查// 表格列表数据 -- 好的代码命名一看就知道是什么意思,是非常棒的const tableData = ref<any[]>([])// 输入框框实例const searchFormRef = ref<FormInstance |null>(null)// 输入框输入值const searchData =reactive({username:"",phone:""})// 获取所有列表数据进行页面的渲染constgetTableData=()=>{
  loading.value =true// 获取列表数据的 APIgetTableDataApi({// 前俩个看命名也知道是分页相关值currentPage: paginationData.currentPage,size: paginationData.pageSize,username: searchData.username ||undefined,phone: searchData.phone ||undefined}).then((res)=>{// 总数以及列表数据
      paginationData.total = res.data.total
      tableData.value = res.data.list
    }).catch(()=>{// 如果数据请求发生错误,那么不显示数据列表
      tableData.value =[]}).finally(()=>{// 无论请求成功或者失败 不显示加载图标
      loading.value =false})}// 查询consthandleSearch=()=>{// 只有当数据处在第一页的时候才会刷新数据if(paginationData.currentPage ===1){getTableData()}// 跳转到第一页
  paginationData.currentPage =1}// 重置constresetSearch=()=>{// resetFields 重置该表单项,将其值重置为初始值,并移除校验结果,这也是 element-ui-plus 组件实例上的方法
  searchFormRef.value?.resetFields()if(paginationData.currentPage ===1){getTableData()}
  paginationData.currentPage =1}// 刷新表格consthandleRefresh=()=>{getTableData()}//#endregion/** 监听分页参数的变化 */// 看到这里我们就明白为什么查询和重置页面为什么跳转到第一页就不管了,因为这里在监听着分页参数的变化,这样的完美代码看着是很爽的,为作者点一个大赞watch([()=> paginationData.currentPage,()=> paginationData.pageSize], getTableData,{immediate:true})</script><template><divclass="app-container"><el-cardv-loading="loading"shadow="never"class="search-wrapper"><el-formref="searchFormRef":inline="true":model="searchData"><el-form-itemprop="username"label="用户名"><el-inputv-model="searchData.username"placeholder="请输入"/></el-form-item><el-form-itemprop="phone"label="手机号"><el-inputv-model="searchData.phone"placeholder="请输入"/></el-form-item><el-form-item><el-buttontype="primary":icon="Search"@click="handleSearch">查询</el-button><el-button:icon="Refresh"@click="resetSearch">重置</el-button></el-form-item></el-form></el-card><el-cardv-loading="loading"shadow="never"><divclass="toolbar-wrapper"><div><el-buttontype="primary":icon="CirclePlus"@click="dialogVisible = true">新增用户</el-button><el-buttontype="danger":icon="Delete">批量删除</el-button></div><div><el-tooltipcontent="下载"><el-buttontype="primary":icon="Download"circle/></el-tooltip><el-tooltipcontent="刷新表格"><el-buttontype="primary":icon="RefreshRight"circle@click="handleRefresh"/></el-tooltip></div></div><divclass="table-wrapper"><el-table:data="tableData"><el-table-columntype="selection"width="50"align="center"/><el-table-columnprop="username"label="用户名"align="center"/><el-table-columnprop="roles"label="角色"align="center"><template#default="scope"><el-tagv-if="scope.row.roles === 'admin'"effect="plain">admin</el-tag><el-tagv-elsetype="warning"effect="plain">{{ scope.row.roles }}</el-tag></template></el-table-column><el-table-columnprop="phone"label="手机号"align="center"/><el-table-columnprop="email"label="邮箱"align="center"/><el-table-columnprop="status"label="状态"align="center"><template#default="scope"><el-tagv-if="scope.row.status"type="success"effect="plain">启用</el-tag><el-tagv-elsetype="danger"effect="plain">禁用</el-tag></template></el-table-column><el-table-columnprop="createTime"label="创建时间"align="center"/><el-table-columnfixed="right"label="操作"width="150"align="center"><template#default="scope"><el-buttontype="primary"textbgsize="small"@click="handleUpdate(scope.row)">修改</el-button><el-buttontype="danger"textbgsize="small"@click="handleDelete(scope.row)">删除</el-button></template></el-table-column></el-table></div><divclass="pager-wrapper"><el-paginationbackground:layout="paginationData.layout":page-sizes="paginationData.pageSizes":total="paginationData.total":page-size="paginationData.pageSize":currentPage="paginationData.currentPage"@size-change="handleSizeChange"@current-change="handleCurrentChange"/></div></el-card><!-- 新增/修改 --><el-dialogv-model="dialogVisible":title="currentUpdateId === undefined ? '新增用户' : '修改用户'"@close="resetForm"width="30%"><el-formref="formRef":model="formData":rules="formRules"label-width="100px"label-position="left"><el-form-itemprop="username"label="用户名"><el-inputv-model="formData.username"placeholder="请输入"/></el-form-item><el-form-itemprop="password"label="密码"><el-inputv-model="formData.password"placeholder="请输入"/></el-form-item></el-form><template#footer><el-button@click="dialogVisible = false">取消</el-button><el-buttontype="primary"@click="handleCreate">确认</el-button></template></el-dialog></div></template><stylelang="scss"scoped>.search-wrapper{margin-bottom: 20px;:deep(.el-card__body){padding-bottom: 2px;}}.toolbar-wrapper{display: flex;justify-content: space-between;margin-bottom: 20px;}.table-wrapper{margin-bottom: 20px;}.pager-wrapper{display: flex;justify-content: flex-end;}</style>

分页数据作者是怎么处理的 usePagination

这是分页数据接口规范以及方法定义导出

import{ reactive }from"vue"// ts 定义接口 -- 分页数据接口规范interfaceIDefaultPaginationData{total: number
  currentPage: number
  pageSizes: number[]pageSize: number
  layout: string
}// ts 定义接口 -- 合并数据接口规范interfaceIPaginationData{
  total?: number
  currentPage?: number
  pageSizes?: number[]
  pageSize?: number
  layout?: string
}/** 默认的分页参数 */constdefaultPaginationData: IDefaultPaginationData ={total:0,currentPage:1,pageSizes:[10,20,50],pageSize:10,layout:"total, sizes, prev, pager, next, jumper"}exportfunctionusePagination(_paginationData: IPaginationData ={}){/** 合并分页参数 */// Object.assign()是对象的静态方法,可以用来复制对象的可枚举属性到目标对象,利用这个特性可以实现对象属性的合并// 意思就是传过来的值有的话就覆盖,没有就使用默认分页数据,这个处理很完美const paginationData =reactive(Object.assign({...defaultPaginationData }, _paginationData))/** 改变当前页码 */consthandleCurrentChange=(value: number)=>{
    paginationData.currentPage = value
  }/** 改变每页显示数据数量 */consthandleSizeChange=(value: number)=>{
    paginationData.pageSize = value
  }return{ paginationData, handleCurrentChange, handleSizeChange }}}

顺藤摸瓜找到 api 接口的封装

我们顺着上面发起请求的导出方法找到了这里(这里位于 src 下的 api 文件夹),这是一些简单的接口定义以及 api 接口的封装,等等,好像有一个奇怪的东西,在依赖包中使用的是 axios ,怎么出现了 request ,肯定还有一个整体封装层,并且应该在那里会有一个请求响应拦截器,我们去看看

import{ request }from"@/utils/service"interfaceICreateTableRequestData{
  username:string
  password:string}interfaceIUpdateTableRequestData{
  id:string
  username:string
  password?:string}interfaceIGetTableRequestData{/** 当前页码 */
  currentPage:number/** 查询条数 */
  size:number/** 查询参数 */
  username?:string
  phone?:string}typeGetTableResponseData= IApiResponseData<{
  list:{
    createTime:string
    email:string
    id:string
    phone:string
    roles:string
    status:boolean
    username:string}[]
  total:number}>/** 增 */exportfunctioncreateTableDataApi(data: ICreateTableRequestData){returnrequest({
    url:"table",
    method:"post",
    data
  })}/** 删 */exportfunctiondeleteTableDataApi(id:string){returnrequest({
    url:`table/${id}`,
    method:"delete"})}/** 改 */exportfunctionupdateTableDataApi(data: IUpdateTableRequestData){returnrequest({
    url:"table",
    method:"put",
    data
  })}/** 查 */exportfunctiongetTableDataApi(params: IGetTableRequestData){returnrequest<GetTableResponseData>({
    url:"table",
    method:"get",
    params
  })}

api 接口再往底层找全局请求封装与请求拦截器 service.ts

果然是 😎

import axios,{typeAxiosInstance,typeAxiosRequestConfig}from"axios"import{ useUserStoreHook }from"@/store/modules/user"import{ ElMessage }from"element-plus"import{ get }from"lodash-es"import{ getToken }from"./cache/cookies"/** 创建请求实例 */functioncreateService(){// 创建一个 Axios 实例const service = axios.create()// 请求拦截
  service.interceptors.request.use((config)=> config,// 发送失败(error)=>Promise.reject(error))// 响应拦截(可根据具体业务作出相应的调整)
  service.interceptors.response.use((response)=>{// apiData 是 API 返回的数据const apiData = response.data asany// 这个 Code 是和后端约定的业务 Codeconst code = apiData.code
      // 如果没有 Code, 代表这不是项目后端开发的 APIif(code ===undefined){
        ElMessage.error("非本系统的接口")returnPromise.reject(newError("非本系统的接口"))}else{switch(code){case0:// code === 0 代表没有错误return apiData
          default:// 不是正确的 Code
            ElMessage.error(apiData.message ||"Error")returnPromise.reject(newError("Error"))}}},(error)=>{// Status 是 HTTP 状态码const status =get(error,"response.status")switch(status){case400:
          error.message ="请求错误"breakcase401:// Token 过期时,直接退出登录并强制刷新页面(会重定向到登录页)useUserStoreHook().logout()
          location.reload()breakcase403:
          error.message ="拒绝访问"breakcase404:
          error.message ="请求地址出错"breakcase408:
          error.message ="请求超时"breakcase500:
          error.message ="服务器内部错误"breakcase501:
          error.message ="服务未实现"breakcase502:
          error.message ="网关错误"breakcase503:
          error.message ="服务不可用"breakcase504:
          error.message ="网关超时"breakcase505:
          error.message ="HTTP 版本不受支持"breakdefault:break}
      ElMessage.error(error.message)returnPromise.reject(error)})return service
}/** 创建请求方法 */functioncreateRequestFunction(service: AxiosInstance){returnfunction<T>(config: AxiosRequestConfig):Promise<T>{const configDefault ={
      headers:{// 携带 Token
        Authorization:"Bearer "+getToken(),"Content-Type":get(config,"headers.Content-Type","application/json")},
      timeout:5000,
      baseURL:import.meta.env.VITE_BASE_API,
      data:{}}returnservice(Object.assign(configDefault, config))}}/** 用于网络请求的实例 */exportconst service =createService()/** 用于网络请求的方法 */exportconst request =createRequestFunction(service)

本文转载自: https://blog.csdn.net/weixin_63836026/article/details/129127768
版权归原作者 Try Tomato 所有, 如有侵权,请联系我们删除。

“分享项目 - Vue3 + TS + element-ui-plus 项目 -- Table表格表单”的评论:

还没有评论