测试开发前端篇之项目列表页
项目列表页构建
vue_base
├─ .gitignore
├─ auto-imports.d.ts
├─ components.d.ts
├─ index.html
├─ package-lock.json
├─ package.json
├─ public
│ └─ favicon.ico
├─ README.md
├─ src
│ ├─ api
│ │ └─ api.js
│ ├─ App.vue
│ ├─ assets
│ │ ├─ css
│ │ │ └─ main.css
│ │ ├─ logo6.png
│ │ ├─ logoA.png
│ ├─ components
│ │ └─ LoginBack.vue
│ ├─ main.js
│ ├─ router
│ │ └─ index.js
│ ├─ store
│ │ └─ index.js
│ └─ views
│ ├─ AllProject.vue
│ ├─ Login.vue
└─ vite.config.js
这里增加了一个项目列表页AllProject.vue
路由文件修改
// 1.导入createRouter、createWebHashHistory
import {
createRouter,
createWebHashHistory,
createWebHistory,
} from 'vue-router'
// 2.配置路由规则
const routes = [
{
// 指定页面访问的路径
// 必须指定
path: '/',
// 指定该路由的名称
name: 'root',
// 定义当返回路径时,显示的组件
// component: () => import('../views/Home.vue')
// redirect: '/login'
redirect: { name: 'login' } // 将 /home 重定向到名为 dashboard 的路由
},
{
path: '/login',
name: 'login',
component: () => import('../views/Login.vue')
},
{
path: '/allProject',
name: 'allProject',
component: () => import('../views/AllProject.vue'),
meta: {
requiresAuth: true // 需要登录权限
}
},
]
// 3.创建路由实例
const router = createRouter({
// 设置路由模式
// a.hash模式:会在url中带一个#,无需服务器进行额外配置、兼容性好、不利于SEO,搜索引擎不会抓取#后面的内容
history: createWebHashHistory(),
// b.HTML5模式:url中不带#,需要服务器的额外配置(nginx)、兼容性不好、利于SEO
// history: createWebHistory(),
// 指定路由规则数组
// routes: routes
routes
})
// 4.导出路由实例
export default router
这里的路由文件需要注意的是meta
字段中的 requiresAuth: true
表示该路由需要登录权限。这意味着用户在访问此路由之前必须先进行身份验证。与路由守卫配合使用。
meta: {
requiresAuth: true // 需要登录权限
}
封装接口请求增加
import axios from 'axios'
const base_url = 'http://121.4.107.148:18899';
// 创建axios请求实例对象
const httpDev = axios.create({
baseURL: base_url, //定义公共的base url
headers: {
'Content-Type': 'application/json'
},
// 定义校验响应状态码
validateStatus: function(status) {
return status >= 200 && status < 300;
}
})
// 请求拦截器
httpDev.interceptors.request.use(
function(config) {
// 发送请求前判断是否需要鉴权
console.log('请求拦截器:config', config)
if (config.url !== '/login/' && config.url !== '/register/') {
// 添加token
const token = sessionStorage.getItem('token');
if (token) {
// Bearer
config.headers['Authorization'] = 'JWT ' + token;
}
}
return config;
},
function(error) {
// 请求错误时做一些处理
return Promise.reject(error)
}
)
// 响应拦截器
httpDev.interceptors.response.use(
function(response) {
console.log('响应拦截器:response', response)
// 响应数据之前做一些处理
return response
},
function(error) {
// 响应错误时做一些处理
return Promise.reject(error)
}
)
export default {
// 登录接口
loginApi(params) {
return httpDev.post('/login/', params);
},
// 获取项目列表接口
getProjects() {
return httpDev.get('/projects/');
},
// 添加项目接口
createProject(params) {
return httpDev.post('/projects/', params)
},
// 更新项目
updateProject(id, params) {
return httpDev.patch(`/projects/${id}/`, params)
},
// 删除
deleteProject(id,leader) {
return httpDev.delete(`/projects/${id}/?leader=${leader}`)
}
},
}
这里需要注意:反引号(`
)通常用于创建模板字面量,可以让您在字符串中插入变量或表达式,并在需要时进行字符串插值。${
id}
是要插入到字符串中的表达式
(`/projects/${id}/`, params)
(`/projects/${id}/?leader=${leader}`)
${baseUrl}
: 这是 API 的基础 URL 地址,用于构建完整的请求 URL。您需要将其替换为您自己的 API 服务器地址。
/projects/${id}/
: 这部分构成了完整的请求 URL。${id}
是项目的唯一标识符,用于指定要更新的特定项目。
params
: 这是作为请求体发送的数据,包含要更新的项目参数。根据您的 API 的要求,您可能需要以特定的
项目列表组件
<template>
<div class="title" style="margin: 40px 220px 5px;">
<span style="height: 30px; color: #42b983; font: 700 20px/30px microsoft yahei;">项目列表</span>
<el-button type="success" style="float: right; margin-left:10px" @click="logout">
<el-icon><UserFilled /></el-icon>退出登录
</el-button>
<el-button type="success" style="float: right; " @click.stop="handleAdd()">
<el-icon ><Plus /></el-icon>添加项目
</el-button>
<div class="divider"></div>
</div>
<div class="custom-row">
<div v-for="project in projects" :key="project.id" class="card-wrapper" @click="goToProjectDetail(project.id)">
<div style="padding:20px 10px ">
<div class="el-card" style="border: none;">
<el-icon class="monitor-icon"><Monitor /></el-icon>
<el-tooltip :content="project.name" placement="bottom">
<div class="pro_name">{{ project.name }}</div>
</el-tooltip>
<el-tooltip :content="project.leader" placement="top">
<div class="pro_leader">负责人:{{ project.leader }}</div>
</el-tooltip>
<div style="margin-top: 30px;">
<el-button size="small" @click.stop="handleEdit(project)"><el-icon ><Edit /></el-icon>编辑</el-button>
<el-button size="small" type="danger" @click.stop="openDeleteDialog(project)" style="margin-left: 40px;"><el-icon ><Delete /></el-icon>删除</el-button>
</div>
</div>
</div>
</div>
<div class="card-wrapper" @click="handleAdd()">
<el-icon class="plus-icon"><Plus /></el-icon>
</div>
</div>
<!-- 编辑弹框 -->
<el-dialog v-model="showEditDialog" title="修改项目" width="35%">
<template #default>
<div class="dialog-content">
<label for="projectNameInput">项目名</label>
<input id="projectNameInput" v-model="editForm.name" :placeholder="editForm.name ? editForm.name : ''"/>
<br />
<label for="projectLeaderInput">负责人</label>
<input id="projectLeaderInput" v-model="editForm.leader" :placeholder="editForm.leader ? editForm.leader : ''"/>
</div>
</template>
<template #footer>
<span class="dialog-footer">
<el-button @click="cancelEdit">取消</el-button>
<el-button type="primary" @click="confirmEdit">确定</el-button>
</span>
</template>
</el-dialog>
<!-- 删除弹框 -->
<el-dialog v-model="showDelDialog" title="提示" width="35%">
<div style="display: flex; align-items: center;">
<el-icon style="font-size: 24px; color: rgb(233, 226, 125);"><WarningFilled /></el-icon>
<span style="margin-left: 10px;">确定要删除该项目吗?</span>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="cancelDel">取消</el-button>
<el-button type="primary" @click="confirmDel">确定</el-button>
</span>
</template>
</el-dialog>
<!-- 添加项目弹框 -->
<el-dialog v-model="showAddDialog" title="添加项目" width="35%" >
<template #default>
<div class="dialog-content">
<label for="">项目名</label>
<input v-model="addForm.name" placeholder="" autocomplete="off"/>
<br />
<label for="">负责人</label>
<input v-model="addForm.leader" placeholder="" autocomplete="off" />
</div>
</template>
<template #footer>
<span class="dialog-footer">
<el-button @click="cancelEdit">取消</el-button>
<el-button type="primary" @click="confirmAdd">确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script>
export default {
data() {
return {
// 项目列表
projects: [],
// 定义项目id
id:'',
// 编辑默认弹框
showEditDialog: false,
// 添加项目默认弹框
showAddDialog:false,
// 删除默认弹框
showDelDialog: false,
// 用于接收用户编辑的项目信息
editForm: {
name: '',
leader: '',
},
// 用于接收用户输入的项目信息
addForm:{
name:'',
leader:'',
}
}
},
mounted() {
this.fetchProjectInfo();
const redirectedFromLogin = sessionStorage.getItem('redirectedFromLogin');
console.log(redirectedFromLogin)
if (redirectedFromLogin) {
// 显示提示框或弹出框
// 可以使用第三方库如 element-ui、vant 等,或自己实现弹出框逻辑
this.showLoginSuccessMessage()
// 清除跳转标记
sessionStorage.removeItem('redirectedFromLogin');
}
},
methods: {
// 获取所有项目信息接口
async fetchProjectInfo() {
try {
// const response = await httpDev.get('/projects/');
const response = await this.$api.getProjects();
this.projects = response.data;
console.log('项目信息:', this.projects);
// 在这里将获取到的项目信息保存到组件的data属性中,以便在模板中使用
} catch (error) {
console.error('获取项目信息失败', error);
}
},
// 添加项目接口
async clickaddProject() {
try {
// const response = await httpDev.post('/projects/',this.addForm);
const response = await this.$api.createProject(this.addForm)
if (response.status == 201){
ElMessage({
message: '添加成功!',
type: 'success',
})
this.fetchProjectInfo()
}
} catch (error) {
console.error('添加项目失败', error);
}
},
// 删除项目接口
async clickdelProject() {
try {
const id = this.id;
const leader = this.editForm.leader;
const response = await this.$api.deleteProject(id,leader)
if (response.status == 204){
ElMessage({
message: '删除成功!',
type: 'success',
})
this.fetchProjectInfo()
}
} catch (error) {
console.error('删除项目失败', error);
}
},
// 修改项目接口
async clickeditProject() {
try {
const id = this.id;
const response = await this.$api.updateProject(id,this.editForm)
if (response.status == 200){
ElMessage({
message: '更新成功!',
type: 'success',
})
this.fetchProjectInfo()
}
} catch (error) {
console.error('更新项目失败', error);
}
},
handleEdit(project) {
this.showEditDialog = true;
this.id = project.id;
this.editForm.name = project.name
this.editForm.leader = project.leader
},
cancelEdit() {
this.showEditDialog = false;
this.showAddDialog = false;
},
confirmEdit() {
// 处理编辑确定按钮逻辑
this.clickeditProject()
this.showEditDialog = false;
},
handleAdd() {
this.showAddDialog = true;
},
confirmAdd() {
if (this.addForm.name.trim() === '' || this.addForm.leader.trim() === '') {
ElMessage({
message: '请填写项目名和负责人',
type: 'warning',
})
} else {
// 处理添加确定按钮逻辑
this.clickaddProject();
this.showAddDialog = false;
// 清空输入框中的内容
this.addForm.name = '';
this.addForm.leader = '';
}
},
openDeleteDialog(project) {
this.showDelDialog = true;
this.id = project.id;
this.editForm.leader = project.leader;
},
cancelDel() {
this.showDelDialog = false;
},
confirmDel() {
// 处理删除确定按钮逻辑
this.clickdelProject()
this.showDelDialog = false;
},
logout() {
// 执行退出登录的逻辑
// 重定向到首页
sessionStorage.removeItem('token'); // 假设用于存储用户令牌的键名为'Token'
console.log(sessionStorage.getItem('token'))
this.$router.push('/');
// location.reload(true);
},
showLoginSuccessMessage() {
ElMessage({
message: '登录成功!',
type: 'success',
})
},
goToProjectDetail(id) {
console.log('Navigating to Project with ID:', id);
// 将项目ID保存到会话存储
sessionStorage.setItem('projectId', id);
// 调用 setProjectId mutation 来设置项目ID
// this.$store.commit('setProjectId', id);
this.$router.push({ name: 'Project' });
},
}
}
</script>
<style>
.custom-row {
display: flex;
margin: 10px calc(14.7%) 60px;
flex-wrap: wrap;
justify-content:flex-start;
}
.card-wrapper{
box-shadow: -4px 2px 12px 5px rgb(0 0 0 / 10%);
width: 200px;
height: 250px;
margin: 0 10px 20px 10px;
color: #fff;
text-align: center;
}
.pro_leader,
.pro_name{
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.pro_leader{
line-height: 20px;
font-size: 10px;
cursor: pointer;
color: #000;
}
.pro_name{
line-height: 60px;
font-size: 25px;
cursor: pointer;
}
.monitor-icon {
margin-top: 10px;
color: #066a3d;
font-size: 40px;
background: #a3c9bb;
border-radius: 50%;
width: 70px;
height: 70px;
cursor: pointer;
}
.divider {
height: 6px;
background-color: #42b983;
margin-top: 5px;
margin-bottom: 50px;
}
.plus-icon {
margin-top: 80px;
color: rgb(194, 194, 194);
font-size: 80px;
cursor: pointer;
}
.el-select {
width: 300px;
}
.el-input {
width: 300px;
}
.dialog-footer button:first-child {
margin-right: 10px;
}
.dialog-footer button:first-child {
margin-right: 10px;
}
.dialog-content{
margin-left: 30px;
}
.dialog-content input {
width: 70%; /* 调整输入框宽度 */
height: 32px;
line-height: 32px;
margin: 10px;
border-radius: 8px;
}
.el-popper.is-customized {
/* Set padding to ensure the height is 32px */
padding: 3px 12px;
background: linear-gradient(90deg, rgb(159, 229, 151), rgb(204, 229, 129));
}
</style>
这段代码是一个项目列表的前端组件,使用Vue.js和Element UI库进行开发。
该组件包含以下功能:
展示项目列表,循环渲染每个项目的卡片。
可以点击项目卡片进入项目详情页。
可以添加项目,点击“添加项目”按钮会弹出一个对话框,输入项目名和负责人后可以确认添加。
可以编辑项目,点击“编辑”按钮会弹出一个对话框,可以修改项目名和负责人后确认更新。
可以删除项目,点击“删除”按钮会弹出一个确认对话框,确认后会删除该项目。
整个组件的样式使用了一些CSS来设置卡片、对话框等的样式。
另外,组件中还包含了一些接口请求的逻辑,通过调用接口获取项目列表、添加项目、删除项目、更新项目等。
需要注意的是,组件中使用了一些第三方库(如element-ui
)、全局变量(如this.api
)和路由(this.router
),请确保相关依赖已经正确引入,并且项目中已经配置好了路由和接口请求相关的配置。
另外,组件中有一些el-开头的标签,这些是Element UI库提供的组件,需要确保已经正确引入Element UI库。
Element UI弹框的使用
Element UI弹框的使用逻辑如下:
定义弹框的显示状态,通过
v-model
指令实现双向绑定。设置弹框的标题和宽度。
使用
<template>
定义弹框的内容区域和底部按钮。在按钮的点击事件中调用对应的方法,处理确认和取消操作。
我这里用修改项目弹框来解释说明一下:
首先组件模板中有一个编辑的按钮绑定了一个点击事件,在点击编辑按钮的时候弹出修改项目弹框,弹框中有标题、两个输入框、底部两个按钮:取消和确认。
<div style="margin-top: 30px;">
<el-button size="small" @click.stop="handleEdit(project)"><el-icon ><Edit /></el-icon>编辑</el-button>
<el-button size="small" type="danger" @click.stop="openDeleteDialog(project)" style="margin-left: 40px;"><el-icon ><Delete /></el-icon>删除</el-button>
</div>
然后是编辑弹框的模板
<!-- 编辑弹框 -->
<el-dialog v-model="showEditDialog" title="修改项目" width="35%">
<template #default>
<div class="dialog-content">
<label for="projectNameInput">项目名</label>
<input id="projectNameInput" v-model="editForm.name" :placeholder="editForm.name ? editForm.name : ''"/>
<br />
<label for="projectLeaderInput">负责人</label>
<input id="projectLeaderInput" v-model="editForm.leader" :placeholder="editForm.leader ? editForm.leader : ''"/>
</div>
</template>
<template #footer>
<span class="dialog-footer">
<el-button @click="cancelEdit">取消</el-button>
<el-button type="primary" @click="confirmEdit">确定</el-button>
</span>
</template>
</el-dialog>
弹框的显示与隐藏:
v-model="showEditDialog"
将showEditDialog
与弹框的显示状态进行双向绑定。
弹框的标题和宽度:
title="修改项目"
设置弹框的标题为"修改项目"。width="35%"
设置弹框的宽度为35%。
弹框的内容:
<template #default>...</template>
定义了弹框的内容区域。在内容区域中,包含两个输入框和对应的标签。其中,项目名输入框使用
v-model="editForm.name"
进行数据绑定,负责人输入框使用v-model="editForm.leader"
进行数据绑定。输入框的placeholder属性,通过三元表达式动态绑定了
editForm.name
和editForm.leader
的值。
弹框的底部按钮:
<template #footer>...</template>
定义了弹框的底部按钮区域。弹框底部的按钮包含两个:取消按钮和确定按钮。
在取消按钮的点击事件中,调用
cancelEdit
方法进行相应的操作。在确定按钮的点击事件中,调用
confirmEdit
方法进行相应的操作。
总结一下,该弹框组件实现了一个用于修改项目信息的弹框,其中包含了项目名和负责人两个输入框,并且可以通过取消和确定按钮进行
初始数据的定义
export default {
data() {
return {
// 项目列表
projects: [],
// 定义项目id
id:'',
// 编辑默认弹框
showEditDialog: false,
// 添加项目默认弹框
showAddDialog:false,
// 删除默认弹框
showDelDialog: false,
// 用于接收用户编辑的项目信息
editForm: {
name: '',
leader: '',
},
// 用于接收用户输入的项目信息
addForm:{
name:'',
leader:'',
}
}
},
这里定义了编辑弹框的状态一开始是关闭状态的
最后是点击事件中调用对应的方法,处理确认和取消操作。
handleEdit(project) {
this.showEditDialog = true;
this.id = project.id;
this.editForm.name = project.name
this.editForm.leader = project.leader
},
cancelEdit() {
this.showEditDialog = false;
},
confirmEdit() {
// 处理编辑确定按钮逻辑
this.clickeditProject()
this.showEditDialog = false;
},
handleEdit
方法用于打开编辑弹框并展示项目的原始信息,cancelEdit
方法用于关闭编辑和添加弹框,confirmEdit
方法用于处理编辑弹框中确定按钮的逻辑,并关闭编辑弹框。
接口请求
methods: {
// 获取所有项目信息接口
async fetchProjectInfo() {
try {
// const response = await httpDev.get('/projects/');
const response = await this.$api.getProjects();
this.projects = response.data;
console.log('项目信息:', this.projects);
// 在这里将获取到的项目信息保存到组件的data属性中,以便在模板中使用
} catch (error) {
console.error('获取项目信息失败', error);
}
},
// 添加项目接口
async clickaddProject() {
try {
// const response = await httpDev.post('/projects/',this.addForm);
const response = await this.$api.createProject(this.addForm)
if (response.status == 201){
ElMessage({
message: '添加成功!',
type: 'success',
})
this.fetchProjectInfo()
}
} catch (error) {
console.error('添加项目失败', error);
}
},
// 删除项目接口
async clickdelProject() {
try {
const id = this.id;
const leader = this.editForm.leader;
const response = await this.$api.deleteProject(id,leader)
if (response.status == 204){
ElMessage({
message: '删除成功!',
type: 'success',
})
this.fetchProjectInfo()
}
} catch (error) {
console.error('删除项目失败', error);
}
},
// 修改项目接口
async clickeditProject() {
try {
const id = this.id;
const response = await this.$api.updateProject(id,this.editForm)
if (response.status == 200){
ElMessage({
message: '更新成功!',
type: 'success',
})
this.fetchProjectInfo()
}
} catch (error) {
console.error('更新项目失败', error);
}
},
}
这里定义了几个方法:fetchProjectInfo
、clickaddProject
、clickdelProject
和clickeditProject
,分别用于获取所有项目信息、添加项目、删除项目和修改项目。
fetchProjectInfo
方法:该方法用于通过调用异步接口获取所有项目的信息。
clickaddProject
方法:该方法用于添加项目。
clickdelProject
方法:该方法用于删除项目。
clickeditProject
方法:该方法用于修改项目。
mounted
钩子函数
mounted
钩子函数是在组件挂载后执行的生命周期钩子。
mounted() {
this.fetchProjectInfo();
const redirectedFromLogin = sessionStorage.getItem('redirectedFromLogin');
console.log(redirectedFromLogin)
if (redirectedFromLogin) {
// 显示提示框或弹出框
// 可以使用第三方库如 element-ui、vant 等,或自己实现弹出框逻辑
this.showLoginSuccessMessage()
// 清除跳转标记
sessionStorage.removeItem('redirectedFromLogin');
}
},
在该钩子函数中,调用了 fetchProjectInfo()
方法来获取项目信息。
此外,还检查了 sessionStorage
中是否存在名为 redirectedFromLogin
的键。如果存在,表示用户刚刚从登录页面重定向到当前页面。在这种情况下,您会显示一个提示框或弹出框来通知用户登录成功。
然后,调用了 showLoginSuccessMessage()
方法,用于显示登录成功的提示。
最后,清除了 sessionStorage
中的 redirectedFromLogin
键,以便在用户刷新页面或导航到其他页面时不再显示登录成功提示。
评论