likes
comments
collection

全栈开发—Vue+Element+Koa 实战发布平台CRUD!

作者站长头像
站长
· 阅读数 12

哈喽大家好!这篇文章来到了发布平台全栈开发的前端部分,终于回到我们最熟悉的前端了!本文将接着前面三篇文章,展开介绍发布平台的前端实战!依然是熟悉的手把手,看完不懂你直接打我!

系列文章:

  1. 总览前端自动化部署流程,如何实现前端发布平台?文章链接
  2. 前端发布平台 node server 实战!文章链接
  3. 前端发布平台 jenkins 实战!如何实现前端自动化部署?文章链接
  4. 前端发布平台全栈实战(前后端开发完整篇)!开发一个前端发布平台
  5. websocket 全栈实战,实现唯一构建实例 + 日志同步文章链接

回顾前面三篇文章,笔者分别介绍了:前端项目的发布全貌、node server实战开发、触发 jenkins 实现自动化部署。本文是第四篇「前端发布平台全栈实战——前端篇」的实战记录分享,主要通过分享 Vue + Element-plus + Koa 实现一个发布平台的前端。下面是本文需要实现的核心点:

  1. 前端后端数据分页展示实现
  2. 全栈增删改数据实现

就怕只写前端的实战对大家来太没意思了,看都看不下去!那笔者就趁机在 demo项目 中使用最近比较火的 tailwindcss 来玩玩,因为实在不想再给需要设置样式的 dom 起类名了(就凭这一点还是很香的)!这里是 vue3 项目安装 tailwindcss文档,如果你也想玩玩赶紧在项目里面装一个,配合插件使用更爽!下面进入文章主题。let's go!!!

快速看源码

一、构建配置前端显示

回顾第一篇文章,那时候已经新建好了一个前端项目了,我们接着之前的开干。在正式进入实战开发之前,准备工作还是不能少的,前端项目还是要装一些包来协助我们的,比如 axioselement-plusvue-router 这些,都是大家非常熟悉的依赖包了,直接安装即可。笔者已经自己完成安装,直接从代码实战开始讲起。

1. 分页展示前端模板

之前在讲 实战Koa 的时候,我们已经可以通过 postman 请求后端接口实现配置数据的创建、保存。现在,我们实现一下在前端将数据展示出来。由于配置数据可能有很多条,于是笔者打算通过 table + 分页 的形式展示数据。

首先先搞个简单的静态界面:

<!-- 写 tailwindcss 后再也不用想类名了! -->
<el-card class="w-screen">
  <template #header>
    <span class="font-bold">项目配置</span>
  </template>
  <!-- 筛选框部分 -->
  <el-form inline>
    <el-form-item label="项目名称"></el-form-item>
  </el-form>
  <!-- 表格部分 -->
  <el-table>
    <el-table-column label="项目名称" prop="projectName" />
    ...
  </el-table>
  <!-- 翻页 -->
  <el-pagination ... />
</el-card>

ToB 的同学在哪里,这玩意很熟悉有木有?现在静态的前端界面就出来了,接下来就是要通过 axios 去服务端请求配置数据,实现前端界面动态接入数据啦~ 全栈开发—Vue+Element+Koa 实战发布平台CRUD!

2. 后端实现分页查询

回顾之前的文章,笔者在后端实战中实现了配置的 创建更新删除 的接口,但是并没有实现 分页查询 的接口,所以在完成前端开发之前,首先需要完成分页接口。所以,笔者在之前的 route 中新建一个路由:

全栈开发—Vue+Element+Koa 实战发布平台CRUD!

首先我们需要实现 getConfigList 的接口,核心代码如下:

export async function getConfigList (ctx, next) {
  try {
    // 获取分页参数(由前端带上来)
    const { pageNo: page, pageSize, projectName } = ctx.request.query
    // 调用 services 层的 findJobPage 实现分页数据查询
    const pageData = await services.findJobPage(page, pageSize, { projectName })
    // 查询成功的返回
    ctx.state.apiResponse = {
      code: RESPONSE_CODE.SUC,
      data: pageData
    }
  } catch (e) {
    ctx.state.apiResponse = {
      code: RESPONSE_CODE.ERR,
      msg: '配置分页查询失败'
    }
  }
  next()
}

很简单是吧,都是之前我们搞过的。接下来具体看看 services.findJobPage 的代码实现:

export async function findJobPage (page, pageSize, params) {
  // 过滤掉前端传控制的搜索条件
  Object.keys(params).forEach(key => {
    if (!params[key]) Reflect.deleteProperty(params, key)
  })
  /**
   * find() 查询数据
   * skip() 故名思义跳过:指定跳过的文档条数
   * limit() 最大条数
  **/
  const DocumentUserList = await JobModel.find(params)
    .skip((page - 1) * pageSize)
    .limit(pageSize)
  // toObject() 将查询出来的 Document 对象转换成 js 对象
  return DocumentUserList.map(_ => _.toObject())
}

其中的数据库交互都是直接调用 mongoose 提供的 api ,在使用层面对笔者这种后端菜鸟来说还是相对简单的。想要了解更多 api 的可以戳 mongoose文档,需要什么接口尽管往里面找,实现 CRUD ,甚至更复杂的业务都完全不在话下啦。

好了,这里我们已经实现完配置的分页查询接口了,我们马不停蹄地到前端页面的开发中!首先我们需要在前端中发起请求,来验证一下我们实现的接口能不能运行!接着往下看👇🏻

3. 前端接入分页接口

前后端正式联调之前,我们需要先把前端项目的 proxy 设置一下,在 vite.config 中:

proxy: {
  '/api': {
    target: 'http://localhost:3200/', // 代理到后端地址
    changeOrigin: true,
    rewrite: path => {
      return path.replace(/^/api/, '')
    }
  }
}

接下来我们通过 axios 实现对后端接口的请求,代码如下:

export async function getConfig (params) {
  return axios.get('/job', { params })
}

紧接着,笔者在 onMounted 阶段进行数据请求,执行请求函数,结果如图所示。可知,我们已经可以从刚才实现的接口中获取到对应的数据了。接下来只需要把数据信息跟前端的 table 组件接上去就可以实现数据展示了! 全栈开发—Vue+Element+Koa 实战发布平台CRUD!

当然,到这一步的时候,笔者才发现忘记对 axios 的返回做拦截了,导致在业务层中获取数据的时候需要通过 data.data 才能获取到,看起来实在是有点不雅,所以笔者决定补回 axios 的返回拦截!根据 axios 的拦截写法实现拦截,代码如下:

axios.interceptors.response.use(function (response) {
  const data = response.data
  if (data.code === 0) {
    return data.data // 这样在业务层就不需要双重 data 获取数据了
  }
  data.message = data.message || data.msg
  return Promise.reject(data)
})

回到获取接口数据的代码中,业务层的实现其实非常简单,笔者通过一个名为 tableDataref 响应式数据获取数据,然后传入到 el-tabledata 属性中。详细的就不展开了,还是比较简单的,笔者这里就贴个伪代码和最终的现实图吧:

  // 获取数据
  tableData.value = await getConfig(searchParams)
  
  <el-table :data="tableData">
    ...
  </el-table>

全栈开发—Vue+Element+Koa 实战发布平台CRUD!

从上图中可以看出,整个 table 的数据接入已经完成,看起来已经有点模样了。但是,功能并没有完全实现,回想一下刚才我们实现分页查询的接口中有没有遗漏了哪一个步骤!没错,就是漏了实现 total 这个字段,我们刚才只是完成了分页数据的查询,但是并没有管 total 字段~接下来我们继续完善我们的分页接口。

其实 total 更简单!我们把同样的分页查询条件通过 mongoosecount() 函数执行一下就完成了~就是这么简单,直接看代码吧:

export function countJob (params) {
  // 处理为空的查询条件
  Object.keys(params).forEach(key => {
    if (!params[key]) Reflect.deleteProperty(params, key)
  })
  // 通过 count 得到 total
  return JobModel.count(params)
}

既然现在需要把 total 字段返回前端,那我们的返回数据的格式就不能简单的返回一个 分页list 数据,所以笔者这里通过对象把它们包起来,把数据一起返回去前端~稍微改造一下返回的代码,具体返回如下:

ctx.state.apiResponse = {
  code: RESPONSE_CODE.SUC,
  data: {
    list: pageData,
    page,
    pageSize,
    total
  }
}

现在,再在前端通过接口查询,看看返回的数据!如下图所示: 全栈开发—Vue+Element+Koa 实战发布平台CRUD!

嗯,有内味了。平时跟后端联调是不是经常能看到这一串!现在我们自己也实现了,哈哈哈,全栈还挺好玩~那现在前端接入数据这部分已经完成了,接下来我们快速把 配置新建配置修改配置删除的也一起完成了!

二、全栈开发实现 CRUD

1. 新建配置

新建的接口在之前的 Koa 实战中已经实现了,所以这里我们只需要实现前端界面并联调好后端的接口就好了,非常 easy ~既然是需要新增,笔者这里决定采用弹窗的交互形式去实现。当用户点击一个“新增配置”的按钮时,弹起弹窗,弹窗的具体内容就是一个表单。事不宜迟,直接上代码:

  1. 首先要增加一个新建的按钮:

    <el-button type="" @click="">新增配置</el-button>
    

    效果如图所示: 全栈开发—Vue+Element+Koa 实战发布平台CRUD!

  2. 新建一个新增、编辑的弹窗(编辑时默认回填数据):

    <el-dialog v-model="dialogVisible" :title="dialogTitle">
      <el-form :model="formData">
        <el-form-item label="项目名称" prop="projectName">
          <el-input v-model="formData.projectName" placeholder="输入项目名称" />
        </el-form-item>
        ......
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="onSubmit">确认</el-button>
        </span>
      </template>
    </el-dialog>
    

    弹窗效果如图所示: 全栈开发—Vue+Element+Koa 实战发布平台CRUD!

  3. 实现新增配置信息函数 onSubmit (用户点击弹窗的“确认”时触发):

    // 响应式数据 formData ,已经绑定在配置弹窗对应表单控件的 v-model 中
    const formData = reactive({
      projectName: '',
      gitUrl: '',
      gitBranch: '',
      buildCommand: '',
      uploadPath: ''
    })
    
    const onSubmit = async () => {
      try {
        // 调用保存函数,传入参数 formData
        await postSave(formData)
        // 保存成功提示
        ElMessage.success('配置保存成功')
        // 保存成功后更新当前页面数据
        await initData()
        // 关闭配置弹窗
        dialogVisible.value = false
      } catch (e) {
        ElMessage.error('配置保存失败')
      }
    }
    

以上就是整个新增配置的前端代码实现了,接下来我们就一起来看看效果。笔者点击 新建配置 按钮,并在弹窗中输入如下配置信息: 全栈开发—Vue+Element+Koa 实战发布平台CRUD!

紧接着笔者打开 控制台 ,点击确认按钮后查看 netWork 信息如下: 全栈开发—Vue+Element+Koa 实战发布平台CRUD! 如图可以看到,这里成功调用了 save 接口实现了数据的保存。并且在前端页面中,也已经出现了我们新增的内容。新增配置功能大功告成!我们趁热打铁,接着实现编辑功能!

2. 编辑配置

实现编辑配置的功能,我们需要让后端知道我们要修改的是哪一条数据,所以我们最好是通过 id(唯一) 去请求后端,然后再带上我们编辑后的数据。所以按照这样的需求场景,最普遍的做法就是在表格中新增一列操作列,并把当前的编辑的数据信息传入处理编辑的函数中。接下来我们通过代码把功能实现一下!

首先我们在 el-table 中增加一列操作列,模板代码如下:

<el-table-column label="操作">
  <template #default="scope">
    <!-- 传入 scopr.row -->
    <el-button type="primary" link @click="onEdit(scope.row)">编辑</el-button>
  </template>
</el-table-column>

当用户点击编辑时,我们需要做两件事:

  1. 打开弹窗
  2. 回填数据

实现 onEdit 函数代码如下:

const onEdit = rowData => {
  // 表示当前时编辑状态
  isEdit.value = true
  // 打开弹窗
  dialogVisible.value = true

  Object.keys(formData).forEach(key => {
    // 根据 rowData 回填 formData 数据
    formData[key] = rowData[key]
  })
  // 获得当前数据的 id
  formData.id = rowData._id
}

由于我们整个提交逻辑跟新增的时候是一样的,所以我们只需要通过 isEdit 来判断一下现在是新增还是编辑,从而去调用不同的接口即可。所以这里我们对上文提到的 onSubmit 函数进行小修改,代码如下:

const onSubmit = async () => {
  try {
    // 通过 isEdit 判断调用新增还是编辑接口
    isEdit.value
      ? await postUpdate(formData)
      : await postSave(formData)
    // 文案根据 isEdit 进行调整
    ElMessage.success(isEdit.value ? '配置编辑成功' : '配置保存成功')
    await initData()
    dialogVisible.value = false
  } catch (e) {
    // 文案根据 isEdit 进行调整
    ElMessage.error(isEdit.value ? '配置编辑失败' : '配置保存失败')
  }
}

验证一下效果,找到一条数据,并在所有数据项后面增加 updata 字符串。然后点击确认按钮。 全栈开发—Vue+Element+Koa 实战发布平台CRUD!

如图所示,调用了 update 接口且正确上传参数(多了个id的参数),并且成功修改数据库数据显示在当前界面中: 全栈开发—Vue+Element+Koa 实战发布平台CRUD!

又搞定了一个功能,现在我们把最后一个功能「删除」也一起实现了,我们接着往下看!

3. 删除配置

删除配置跟编辑一样,也是需要 id(唯一标识) 传给后端,删除对应的配置数据。所以,我们需要在 table 的操作列中新增一个删除的按钮。一般删除这种操作,都是需要二次确认的,所以笔者在这里也应用上 Element-pluscomfirm 组件。

老样子,首先是 前端界面的代码实现:

<!-- 操作列中新增 删除 按钮 -->
<el-popconfirm title="确认删除该配置?" @confirm="onDel(scope.row)">
  <template #reference>
    <el-button type="danger" link>删除</el-button>
  </template>
</el-popconfirm>

效果如图所示: 全栈开发—Vue+Element+Koa 实战发布平台CRUD!

接着我们实现删除函数 onDel

const onDel = async rowData => {
  try {
    // 调用删除请求,传入参数 id
    await postDelete({ id: rowData._id })
    ElMessage.success('配置删除成功')
    // 删除成功后更新当前界面数据
    await initData()
  } catch (e) {
    ElMessage.error('配置删除失败')
  }
}

删除函数的实现也是非常的简单!接下来,笔者就在界面中操作一下,看看删除是否成功实现!笔者在这里就删除一条名为 test add 1 的配置数据,如图所示: 全栈开发—Vue+Element+Koa 实战发布平台CRUD!

点击确认后,成功发起 del 接口的请求,成功删除数据后刷新界面显示,效果如图所示: 全栈开发—Vue+Element+Koa 实战发布平台CRUD!

好啦,到这里整个 crud 的功能就搞定了,也算是体验一把全栈开发的感觉了有木有~在这里,笔者带大家进行简单的回顾本文的内容:

  • 首先是实现分页数据显示,从前端界面到后端接口的实现
  • 然后就是实现了配置信息的 crud 功能!

写在最后

经过本文后,整个专栏的文章编写已经接近尾声了,回顾一下本专栏的内容,我们从 前端后端jenkins 中已经逐渐实现了发布平台的功能了,目前仅剩最后一步:从前端发起构建、获取 jenkins 日志。没错,这一步笔者会在下一篇文章中进行讲解,这里列一下主要分享的内容:

  • 使用 websocket 实现 jenkins 日志传输
  • 后端实现当前构建项目唯一构建实例(多个用户打开同个项目仅有一个构建状态)

大家敬请期待!