测试平台开发前端
需求
测试平台前端界面开发。需要实现登录界面、首页、测试用例页面、测试计划页面、测试记录页面。
实战步骤
初始化项目
- vite 官网文档
- 执行命令:
npm create vite@latest
- 第一步:选择 vue
- 第二步:选择 javascript
- 第三步:
cd vite-project
npm install
npm run dev
使用 vscode 打开项目
- 项目结构
- 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
- 4.调整 html 细节
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>测试平台前端项目</title>
<style>
html,
body,
#app,
.common-layout,
.el-container {
height: 100%;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
封装接口请求
- 新建 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.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;
封装路由
- 新建 router 文件
- 在 router 文件中新建 index.js,封装路由映射
import { createRouter, createWebHashHistory } from "vue-router";
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 = createRouter({
// 使用hash方式实现路由
history: createWebHashHistory(),
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");
- App.vue
<template>
<router-view></router-view>
</template>
<style scoped>
</style>
初始化页面
- 新建 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 style="border-bottom: 1px solid #ccc;">
<div style="display: flex;justify-content:space-between;align-items: center;">
<h1>测试平台</h1>
<div
style="display: flex;flex-direction: row;gap: 10px;justify-content:center;align-items: center;">
<h2>{{ username }}</h2>
<el-button type="primary" @click="logout">退出</el-button>
</div>
</div>
</el-header>
<el-container>
<el-aside width="200px">
<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 style="border-left: 1px solid #ccc;">
<!-- 三个界面,用router-view占位 -->
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue"
import { ElMessage, ElMessageBox } from "element-plus"
import {
Document,
Menu as IconMenu,
Setting,
} from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const logout = () => {
router.push('/user')
}
const username= ref('')
// 初始化数据
const initData = async () => {
const result = await $api.get('/user/get_info')
const { code, data, msg } = result.data
// 成功时
if (code == 0) {
username.value = data
} else {
ElMessage.error(msg)
}
}
// 获取数据操作放在onMounted生命周期中
onMounted(() => {
initData()
})
</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="margin-top: 10px;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="dialogPut = 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('')
// 新增计划[6, 7]
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 (action) => {
console.log('action', action)
if (action == 'confirm') {
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) => {
// [{id: 1, name: '', ..}, {id: 2, name: '', ..}, {id: 3, name: '', ..}]
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>
总结
- 初始化测试平台前端项目
- 开发测试平台前端页面