测试平台vue3+element-plus

一.使用vite初始化项目

二. vscode打开项目

  • 项目结构
    image
  • vscode安装以下插件
    • Chinese (Simplified),为 VS Code 提供本地化界面,按下“Ctrl+Shift+P”组合键以显示“命令面板”,然后键入“display”以筛选并显示“Configure Display Language”命令。按“Enter”,然后会按区域设置显示安装的语言列表,并突出显示当前语言设置
    • Vue.js Extension Pack,用于vue3的智能代码提示,语法高亮、智能感知、Emmet等
    • Prettier - Code formatter,代码格式化
    • Auto Rename Tag,修改 html 标签,自动完成尾部闭合标签的同步修改
    • Auto Close Tag,自动闭合HTML标签
    • Path Intellisense,自动路径补全

三.安装依赖库

  • 1.安装接口请求工具 axios

    npm install axios
    
  • 2.安装路由 vue-router

    npm install vue-router@4
    
  • 3.安装组件库 element-plus

    npm install element-plus
    npm install @element-plus/icons-vue
    

四.封装接口请求

  • 新建api文件
  • 在api文件中新建http.js,初始化axios
    // 完成http请求的基本配置
    // 导入axios
    import axios from "axios"
    
    // 创建axios实例
    var instance = axios.create({
        // 请求体
        headers: {
            'Content-Type': 'application/json'
        },
        // 超时时间
        timeout: 2500,
        // 基础url,后端的接口服务地址
        // baseURL: 'https://dev-hogwarts-platform-backend.hogwarts.ceshiren.com'
        baseURL: 'http://127.0.0.1:5000'
    })
    
    // 添加请求拦截器,在请求头中加入token
    instance.interceptors.request.use(
        config => {
            const token = localStorage.getItem('token')
            // console.log('token', token)
            if (token) {
                // 设置请求头中的 Authorization 字段
                config.headers.Authorization = `Bearer ${token}`;
                // console.log('token', config.headers.Authorization)
            }
            return config
        },
        error => {
            return Promise.reject(error)
        })
    
    export default instance
    
  • 在api文件中新建api.js,封装请求方法
    import instance from './index'
    
    const api = {
      // get方法
      GET(url, params) {
        return instance({
          url: url,
          method: "get",
          params: params
        })
      },
      // get方法
      POST(url, data) {
        return instance({
          url: url,
          method: "post",
          data: data
        })
      },
      // put方法
      PUT(url, data) {
        return instance({
          url: url,
          method: "put",
          data: data
        })
      },
      // delete方法
      DELETE(url, data) {
        return instance({
          url: url,
          method: "delete",
          data: data
        })
      },
    }
    export default api
    
    

五.封装路由

  • 新建router文件
  • 在router文件中新建index.js,封装路由映射
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)
    
    const routes = [
      {
        path: '/',
        redirect: '/index'
      },
      {
        path: '/index',
        name: 'Index',
        component: () => import('../views/Index.vue'),
        children: [
          {
            path: 'testcase',
            name: 'Testcase',
            component: () => import('../views/Testcase.vue'),
          },
          {
            path: 'plan',
            name: 'Plan',
            component: () => import('../views/Plan.vue'),
          },
          {
            path: 'record',
            name: 'Record',
            component: () => import('../views/Record.vue'),
          }
        ]
      },
      {
        path: '/user',
        name: 'User',
        component: () => import('../views/User.vue'),
      },
    ]
    
    const router = new VueRouter({
      routes
    })
    
    // 路由守卫
    router.beforeEach((to, from, next) => {
      // 判断是否要去登录页面
      if (to.path == '/user') {
        // 放行
        next()
      } else {
        // 去其他路由页面,判断是否有登录的token
        const token = localStorage.getItem('token')
        // 如果没有token(未登录)
        if (!token) {
          // 强制进入登录页面
          next('/user')
        } else {
          // 放行
          next()
        }
      }
    })
    
    export default router
    

六.使用依赖包

  • main.js中引入依赖包并使用
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

// 导入api
import api from './api/api'
// 导入路由
import router from './router'
// 导入element-plus
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

// 注册api
window.$api = api

// 初始化vue App
const app = createApp(App)

// 全局引入icon库
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

// 全局引入路由router和element-plus
app.use(router).use(ElementPlus, {locale: zhCn,}).mount('#app')

七.初始化页面

  • 新建views文件,在views文件中新建以下vue文件

  • User.vue

      <template>
          <div>
              User
          </div>
      </template>
      
      <script setup>
      
      </script>
      
      <style scoped>
      
      </style>
    
  • Index.vue

      <template>
          <router-view></router-view>
      </template>
      
      <script setup>
      
      </script>
      
      <style scoped>
      
      </style>
    
  • Testcase.vue

      <template>
          <div>
              Testcase
          </div>
      </template>
      
      <script setup>
      
      </script>
      
      <style scoped>
      
      </style>
    
  • Plan.vue

      <template>
          <div>
              Plan
          </div>
      </template>
      
      <script setup>
      
      </script>
      
      <style scoped>
      
      </style>
    
  • Record.vue

      <template>
          <div>
              Record
          </div>
      </template>
      
      <script setup>
      
      </script>
      
      <style scoped>
      
      </style>
    

八.实现登录页面 User.vue

<template>
    <div class="main">
        <el-card class="box-card" style="margin-bottom: 10%;margin-right: 4%;">
            <template #header>
                <div class="card-header">
                    <span>登录表单</span>
                </div>
            </template>
            <el-form :model="form" label-width="80px">
                <el-form-item label="用户名">
                    <el-input v-model="form.username" />
                </el-form-item>
                <el-form-item label="密码">
                    <el-input type="password" v-model="form.password" />
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="login">登录</el-button>
                    <el-button type="success" @click="register">注册</el-button>
                </el-form-item>
            </el-form>
        </el-card>

    </div>
</template>

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'

const router = useRouter()

const form = ref({
    username: '',
    password: '',
})

// 登录
const login = async () => {
    const { code, data, msg } = await $api.post('/user/login', form.value)
    // 登录成功时
    if (code == 0) {
        ElMessage.success(msg)
        localStorage.setItem('token', data.token)
        router.push('/index/testcase')
    } else {
        ElMessage.error(msg)
    }
}

// 注册
const register = async () => {
    const { code, data, msg } = await $api.post('/user/register', form.value)
    // 注册成功时
    if (code == 0) {
        ElMessage.success(msg)
        login()
    } else {
        ElMessage.error(msg)
    }
}
</script>

<style scoped>
.main {
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    background-image: url('https://ceshiren.com/uploads/default/original/3X/2/8/289416e80d70439819bd6003883d8dfe4e2cd5f3.jpeg');
    background-size: 100% 100%;
}
</style>

八.实现测试用例页面 Testcase.vue

<template>
    <!-- 顶部栏按钮 -->
    <el-button type="success" @click="dialogPlan = true">新增计划</el-button>
    <el-button type="primary" @click="dialogAdd = true">新增用例</el-button>

    <!-- 表格 -->
    <el-table ref="tableRef" stripe border @selection-change="selectionChange" :data="tableData" style="width: 100%">
        <el-table-column type="selection" width="40" />
        <el-table-column prop="id" label="用例Id" width="80" />
        <el-table-column prop="name" label="用例名称" />
        <el-table-column prop="step" label="用例步骤" />
        <el-table-column prop="method" label="用例方法" />
        <el-table-column prop="remark" label="备注" />
        <el-table-column prop="actions" label="操作" width="140">
            <template #default="scope">
                <el-button size="small" type="primary" @click="handleEdit(scope.$index)">修改</el-button>
                <el-button size="small" type="danger" @click="handleDelete(scope.$index)">删除</el-button>
            </template>
        </el-table-column>
    </el-table>

    <!-- 新增计划弹框 -->
    <el-dialog v-model="dialogPlan" title="新增计划">
        <!-- 内容区域 -->
        <el-form>
            <el-form-item label="计划名称">
                <el-input v-model="planName" />
            </el-form-item>
        </el-form>
        <!-- 底部插槽 -->
        <template #footer>
            <span class="dialog-footer">
                <el-button @click="dialogPlan = false">取消</el-button>
                <el-button type="primary" @click="addPlan"> 确认 </el-button>
            </span>
        </template>
    </el-dialog>

    <!-- 新增用例弹框 -->
    <el-dialog v-model="dialogAdd" title="新增用例">
        <!-- 内容区域 -->
        <el-form :model="addData">
            <el-form-item label="用例名称">
                <el-input v-model="addData.name" />
            </el-form-item>
            <el-form-item label="用例步骤">
                <el-input type="textarea" v-model="addData.step" />
            </el-form-item>
            <el-form-item label="用例方法">
                <el-input type="textarea" v-model="addData.method" />
            </el-form-item>
            <el-form-item label="用例备注">
                <el-input type="textarea" v-model="addData.remark" />
            </el-form-item>
        </el-form>
        <!-- 底部插槽 -->
        <template #footer>
            <span class="dialog-footer">
                <el-button @click="dialogAdd = false">取消</el-button>
                <el-button type="primary" @click="addTestcase"> 确认 </el-button>
            </span>
        </template>
    </el-dialog>

    <!-- 修改用例弹框 -->
    <el-dialog v-model="dialogPut" title="修改用例">
        <!-- 内容区域 -->
        <el-form :model="putData">
            <el-form-item label="用例ID">
                <el-input v-model="putData.id" disabled />
            </el-form-item>
            <el-form-item label="用例名称">
                <el-input v-model="putData.name" />
            </el-form-item>
            <el-form-item label="用例步骤">
                <el-input type="textarea" v-model="putData.step" />
            </el-form-item>
            <el-form-item label="用例方法">
                <el-input type="textarea" v-model="putData.method" />
            </el-form-item>
            <el-form-item label="用例备注">
                <el-input type="textarea" v-model="putData.remark" />
            </el-form-item>
        </el-form>
        <!-- 底部插槽 -->
        <template #footer>
            <span class="dialog-footer">
                <el-button @click="dialogAdd = false">取消</el-button>
                <el-button type="primary" @click="putTestcase"> 确认 </el-button>
            </span>
        </template>
    </el-dialog>
</template>

<script setup>
import { onMounted, ref } from "vue"
import { ElMessage, ElMessageBox } from "element-plus"

const tableRef = ref()
// 计划弹框
const dialogPlan = ref(false)
// 计划名称
const planName = ref('')
// 新增计划
const addPlan = async () => {
    let planData = { 'name': planName.value, 'testcase_ids': idList.value }
    const { code, data, msg } = await $api.post('/plan/post', planData)
    // 成功时
    if (code == 0) {
        ElMessage.success(msg)
        dialogPlan.value = false
        planName.value = ''
        tableRef.value.clearSelection()
    } else {
        ElMessage.error(msg)
    }
}

// 新增弹框
const dialogAdd = ref(false)
// 用例数据
const addData = ref({})
// 新增用例
const addTestcase = async () => {
    const { code, data, msg } = await $api.post('/testcase/post', addData.value)
    // 成功时
    if (code == 0) {
        ElMessage.success(msg)
        dialogAdd.value = false
        addData.value = {}
        initData()
    } else {
        ElMessage.error(msg)
    }
}

// 修改弹框
const dialogPut = ref(false)
// 点击修改
const handleEdit = (index) => {
    putData.value = tableData.value[index]
    dialogPut.value = true
}
// 用例数据
const putData = ref({})
// 修改用例
const putTestcase = async () => {
    const { code, data, msg } = await $api.post('/testcase/put', putData.value)
    // 成功时
    if (code == 0) {
        ElMessage.success(msg)
        dialogPut.value = false
        putData.value = {}
        initData()
    } else {
        ElMessage.error(msg)
    }
}

// 删除用例
const handleDelete = (index) => {
    ElMessageBox.alert('请确认是否删除,删除数据后无法找回', '警告!', {
        confirmButtonText: '确认',
        callback: async () => {
            let deleteData = { 'id': tableData.value[index].id }
            const { code, data, msg } = await $api.post('/testcase/delete', deleteData)
            // 成功时
            if (code == 0) {
                ElMessage.success(msg)
                initData()
            } else {
                ElMessage.error(msg)
            }
        },
    })
}

// id列表
const idList = ref([])
// 勾选后会自动触发
const selectionChange = (items) => {
    idList.value = items.map((value) => value.id)
}

// 表格数据
const tableData = ref([])

// 初始化数据
const initData = async () => {
    const { code, data, msg } = await $api.get('/testcase/get')
    // 成功时
    if (code == 0) {
        ElMessage.success(msg)
        tableData.value = data
    } else {
        ElMessage.error(msg)
    }
}

// 获取数据操作放在onMounted生命周期中
onMounted(() => {
    initData()
})
</script>

<style scoped></style>

八.实现测试计划页面 Plan.vue

<template>
    <!-- 表格 -->
    <el-table stripe border :data="tableData" style="width: 100%">
        <el-table-column prop="id" label="计划Id" width="80" />
        <el-table-column prop="name" label="计划名称" />
        <el-table-column prop="testcases" label="用例详情">
            <template #default="scope">
                <span v-for="item in scope.row.testcases">{{ item.name }},</span>
            </template>
        </el-table-column>
        <el-table-column prop="actions" label="操作" width="220">
            <template #default="scope">
                <el-button size="small" type="success" @click="handleBuild(scope.$index)">执行</el-button>
                <el-button size="small" type="primary" @click="getRecord(scope.$index)">历史记录</el-button>
                <el-button size="small" type="danger" @click="handleDelete(scope.$index)">删除</el-button>
            </template>
        </el-table-column>
    </el-table>

    <!-- 历史记录弹框 -->
    <el-dialog v-model="dialogRecord" title="历史记录">
        <!-- 内容区域 -->
        <el-table stripe border :data="recordData" style="width: 100%">
            <el-table-column prop="id" label="记录Id" width="80" />
            <el-table-column prop="plan_id" label="计划Id" width="80" />
            <el-table-column prop="report" label="用例详情">
                <template #default="scope">
                    <a :href="scope.row.report">{{ scope.row.report }}</a>
                </template>
            </el-table-column>
            <el-table-column prop="create_time" label="创建时间" />
        </el-table>
    </el-dialog>
</template>

<script setup>
import { onMounted, ref } from "vue"
import { ElMessage, ElMessageBox } from "element-plus"

// 历史记录弹框
const dialogRecord = ref(false)
// 历史记录数据
const recordData = ref()
// 获取指定计划的报告
const getRecord = async (index) => {
    let getData = { 'plan_id': tableData.value[index].id }
    const { code, data, msg } = await $api.get('/record/get', getData)
    // 成功时
    if (code == 0) {
        ElMessage.success(msg)
        recordData.value = data
    } else {
        ElMessage.error(msg)
    }
    dialogRecord.value = true
}


// 执行
const handleBuild = async (index) => {
    let postData = { 'plan_id': tableData.value[index].id }
    const { code, data, msg } = await $api.post('/record/post', postData)
    // 成功时
    if (code == 0) {
        ElMessage.success(msg)
    } else {
        ElMessage.error(msg)
    }
}

// 删除
const handleDelete = (index) => {
    ElMessageBox.alert('请确认是否删除,删除数据后无法找回', '警告!', {
        confirmButtonText: '确认',
        callback: async () => {
            let deleteData = { 'id': tableData.value[index].id }
            const { code, data, msg } = await $api.post('/plan/delete', deleteData)
            // 成功时
            if (code == 0) {
                ElMessage.success(msg)
                initData()
            } else {
                ElMessage.error(msg)
            }
        },
    })
}

// 表格数据
const tableData = ref([])

// 初始化数据
const initData = async () => {
    const { code, data, msg } = await $api.get('/plan/get')
    // 成功时
    if (code == 0) {
        ElMessage.success(msg)
        tableData.value = data
    } else {
        ElMessage.error(msg)
    }
}

// 获取数据操作放在onMounted生命周期中
onMounted(() => {
    initData()
})
</script>

<style scoped></style>

八.实现测试记录页面 Record.vue

<template>
    <!-- 表格 -->
    <el-table stripe border :data="tableData" style="width: 100%">
        <el-table-column prop="id" label="记录Id" width="180" />
        <el-table-column prop="plan_id" label="计划Id" width="180" />
        <el-table-column prop="report" label="用例详情">
            <template #default="scope">
                <a :href="scope.row.report">{{ scope.row.report }}</a>
            </template>
        </el-table-column>
        <el-table-column prop="create_time" label="创建时间" />
    </el-table>
</template>

<script setup>
import { onMounted, ref } from "vue"
import { ElMessage, ElMessageBox } from "element-plus"

// 表格数据
const tableData = ref([])

// 初始化数据
const initData = async () => {
    const { code, data, msg } = await $api.get('/record/get')
    // 成功时
    if (code == 0) {
        ElMessage.success(msg)
        tableData.value = data
    } else {
        ElMessage.error(msg)
    }
}

// 获取数据操作放在onMounted生命周期中
onMounted(() => {
    initData()
})
</script>

<style scoped></style>