测试平台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

五.封装路由

  • 新建router文件
  • 在router文件中新建index.js,封装路由映射
import instance from './http'

const api = {
  // get方法
  get(url, params) {
    return instance({
      url: url,
      method: "get",
      params: params
    })
  },
  // post方法
  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

六.使用依赖包

  • 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 result = await $api.post('/user/login', form.value)
    const { code, data, msg } = result.data
    console.log(code, data, msg);
    // 登录成功时
    if (code == 0) {
        ElMessage.success(msg)
        localStorage.setItem('token', data.token)
        router.push('/index/testcase')
    } else {
        ElMessage.error(msg)
    }
}

// 注册
const register = async () => {
    const result = await $api.post('/user/register', form.value)
    const { code, data, msg } = result.data
    // 注册成功时
    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>

实现顶部栏侧边栏 Index.vue

<template>
    <div class="common-layout">
        <el-container>
            <el-header>
                <div style="height: 100%;display: flex;justify-content:space-between;align-items: center;">
                    <h1>测试平台</h1>
                    <el-button type="primary" @click="logout">退出</el-button>
                </div>
            </el-header>
            <el-container>
                <el-aside width="200px" style="height: 100vh;">
                    <el-menu router default-active="2" class="el-menu-vertical-demo" @open="handleOpen"
                        @close="handleClose">
                        <el-menu-item index="testcase">
                            <el-icon>
                                <document />
                            </el-icon>
                            <span>测试用例</span>
                        </el-menu-item>
                        <el-menu-item index="plan">
                            <el-icon><icon-menu /></el-icon>
                            <span>测试计划</span>
                        </el-menu-item>
                        <el-menu-item index="record">
                            <el-icon>
                                <setting />
                            </el-icon>
                            <span>测试记录</span>
                        </el-menu-item>
                    </el-menu>
                </el-aside>
                <el-main>
                    <!-- 三个界面,用router-view占位 -->
                    <router-view></router-view>
                </el-main>
            </el-container>
        </el-container>
    </div>
</template>

<script setup>
import {
    Document,
    Menu as IconMenu,
    Setting,
} from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'

const router = useRouter()

const logout = () => {
    router.push('/user')
}
</script>

<style scoped></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 result = await $api.post('/plan/post', planData)
    const { code, data, msg } = result.data
    // 成功时
    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 result = await $api.post('/testcase/post', addData.value)
    const { code, data, msg } = result.data
    // 成功时
    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 result = await $api.post('/testcase/put', putData.value)
    const { code, data, msg } = result.data
    // 成功时
    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 result = await $api.post('/testcase/delete', deleteData)
            const { code, data, msg } = result.data
            // 成功时
            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 result = await $api.get('/testcase/get')
    const { code, data, msg } = result.data
    // 成功时
    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 result = await $api.get('/record/get', getData)
    const { code, data, msg } = result.data
    // 成功时
    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 result = await $api.post('/record/post', postData)
    const { code, data, msg } = result.data
    // 成功时
    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 result = await $api.post('/plan/delete', deleteData)
            const { code, data, msg } = result.data
            // 成功时
            if (code == 0) {
                ElMessage.success(msg)
                initData()
            } else {
                ElMessage.error(msg)
            }
        },
    })
}

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

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

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

<style scoped></style>

个人信息 和 测试用例类型