
my-vue-project/
├── node_modules/ # 依赖包
├── public/ # 静态资源(直接复制到构建结果,不会被 webpack 处理)
│ └── index.html # 应用的 HTML 入口
├── src/ # 源码目录
│ ├── assets/ # 静态资源(图片、字体等,会被构建工具处理)
│ ├── components/ # 可复用的 Vue 组件
│ ├── views/ # 页面级组件(通常与路由对应)
│ ├── router/ # 路由配置(Vue Router)
│ ├── store/ # 状态管理(Vuex 或 Pinia)
│ ├── services/ # API 请求或业务逻辑封装
│ ├── App.vue # 根组件
│ └── main.js # 应用入口文件
├── .env # 环境变量配置
└── package.json # 项目依赖和脚本配置
生产环境下的项目目录结构与上面的差不多

main.js 是项目入口文件,用于初始化 Vue 应用实例,挂载根组件到 DOM。
基础代码:
import { createApp } from'vue'
import App from'./App.vue' // 导入根组件
import router from'./router' // 导入路由配置
import store from'./store' // 导入状态管理
const app = createApp(App)
app.use(router) // 注册路由
app.use(store) // 注册状态管理
app.mount('#app') // 挂载到 public/index.html 中的 #app 元素
App.vue 是整个应用的根组件,通常包含全局布局(如导航栏、页脚)和路由占位符。
基础代码:
<template>
<div id="app">
<nav>...</nav>
<router-view /> <!-- 路由页面会渲染在这里 -->
<footer>...</footer>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style scoped>
/* 全局或局部样式 */
</style>
components/:存放可复用的子组件(如按钮、卡片等)。
views/:存放页面级组件(如 HomeView.vue,AboutView.vue)。
配置页面路由和对应组件的关系
基础代码:
import { createRouter,createWebHistory } from'vue-router'
import HomeView from'../views/HomeView.vue'
const routes = [
{
path: '/',
name: 'home',
component:HomeView // 路由指向页面组件
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue') // 懒加载
}
]
const router = createRouter({
history:createWebHistory(),
routes
})
export default router
集中管理全局状态,如用户的登录信息
基础代码:
import { createStore } from'vuex'
export default createStore({
state: {
count:0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
},1000)
}
}
})
assets/:图片、样式等资源,通过相对路径引用:
<img src="@/assets/logo.png" />
public/:直接复制到构建结果的静态文件,通过绝对路径引用:
<img src="/public-image.png" />
启动应用:main.js 初始化 Vue 实例,挂载 App.vue 到 DOM。
路由匹配:用户访问 URL 时,router 根据路径找到对应的页面组件(如 HomeView.vue)。
组件渲染:页面组件通过 <router-view> 渲染,内部可嵌套子组件(components/)。
状态管理:全局状态通过 store 共享,组件通过 this.$store 或 useStore 访问。
数据流动:父子组件通过 props(父传子)和 $emit(子传父)通信,跨组件通过 store 共享状态。
vue 文件分为 template 部分和 script 部分、style 部分
<template>
<!-- 这里是组件的 HTML 结构 -->
</template>
这是组件的模板部分,定义组件的 HTML 结构
使用 Vue 的特殊指令如 v-if 、 v-for 、 v-model 等
只能有一个根元素
<script>
// 导入其他组件或模块
import ComponentA from'./ComponentA.vue'
export default {
// 组件配置项
name: '组件名',
// 注册子组件
components: {
ComponentA
},
// 组件数据
data() {
return {
message: 'Hello'
}
},
// 方法
methods: {
handleClick() {
// 处理逻辑
}
},
// 生命周期钩子
created() {
// 组件创建时执行
}
}
</script>
主要配置项说明:
用于导入其他组件、工具函数或资源
name: 组件名称
components: 注册子组件
props: 接收父组件传递的数据
data() : 组件的数据
methods: 组件的方法
computed: 计算属性
watch: 监听数据变化
created() : 组件创建完成
mounted() : 组件挂载到 DOM
updated() : 组件更新完成
destroyed() : 组件销毁
<style>
/* CSS 样式 */
</style>
在 Vue.js 中,v- 前缀的语法被称为 指令 (Directives),它们是特殊的 HTML 属性,用于为 DOM 元素添加动态行为。
核心作用:
数据绑定:将数据动态绑定到 DOM(如 v-bind,v-model)
条件渲染:控制元素的显示/隐藏(如 v-if,v-show)
循环渲染:基于数组或对象渲染列表(如 v-for)
事件监听:监听 DOM 事件(如 v-on)
自定义行为:通过自定义指令扩展功能
v-bind:动态绑定 HTML 属性
数据从 Vue 实例流向 DOM 元素,常用指令是 v-bind。
适用于需要将数据动态绑定到元素属性的场景。
示例代码:
<div id="app">
<img v-bind:src="imageUrl"alt="动态图片">
</div>
<script>
new Vue({
el: '#app',
data: {
imageUrl: 'https://example.com/image.jpg'
}
});
</script>
上述代码 imageUrl 的值会动态更新到 src 属性上。
v-bind 可以简写为 :
如:<img:src="imageUrl"alt="动态图片">
支持同时绑定多个属性
<div:class="classObject" :style="styleObject"></div>
data: {
classObject: {
active:true,
'text-danger':false
},
styleObject: {
color: 'red',
fontSize: '16px'
}
}
v-model:双向数据绑定(表单输入)
数据在 DOM 元素与 Vue 实例之间双向流动,常用指令是 v-model。
适用于表单输入等需要实时更新的场景
示例代码:
<div id="app">
<input v-model="message"placeholder="输入消息">
<p>你输入的消息是:{{ message }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
message: ''
}
});
</script>
上述代码当用户在输入框中输入内容时,message 的值会实时更新,反之亦然。
v-model 修饰符:
<input v-model.lazy="message">
<input v-model.number="age">
<input v-model.trim="message">
示例代码:
<div id="app">
<p v-if="isVisible">这个段落是可见的。</p>
<button @click="toggle">切换可见性</button>
</div>
<script>
new Vue({
el: '#app',
data: {
isVisible:true
},
methods: {
toggle() {
this.isVisible = !this.isVisible;
}
}
});
</script>
上述代码,点击按钮会切换 isVisible 的值,从而控制段落的显示与隐藏。
v-else-if 和 v-else 可以与 v-if 一起使用来处理多个条件。
<div id="app">
<p v-if="status === 'loading'">加载中...</p>
<p v-else-if="status === 'success'">加载成功!</p>
<p v-else>加载失败,请重试。</p>
</div>
<script>
new Vue({
el: '#app',
data: {
status: 'loading' // 可以是 'loading', 'success', 'error'
}
});
</script>
示例代码:
<div id="app">
<p v-show="isVisible">这个段落是可见的。</p>
<button @click="toggle">切换可见性</button>
</div>
<script>
new Vue({
el: '#app',
data: {
isVisible:true
},
methods: {
toggle() {
this.isVisible = !this.isVisible;
}
}
});
</script>
上述代码中,v-show 通过设置 display:none 来控制段落的显示与隐藏。
v-if 和 v-show 的区别
v-if:
元素在条件为假时不会被渲染到 DOM 中。
适合用于条件变化频率较低的场景。
v-show:
元素始终会被渲染到 DOM 中,只是通过 CSS 控制显示与隐藏。
适合用于频繁切换的场景,因为切换的性能开销更小。
示例代码:
<div id="app">
<ul>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
</div>
<script>
new Vue({
el: '#app',
data: {
items: [
{ id:1,name: '苹果' },
{ id:2,name: '香蕉' },
{ id:3,name: '橙子' }
]
}
});
</script>
上述代码,v-for 指令遍历 items 数组,并为每个元素生成一个列表项。
基本语法:
v-for=“(item,index)in items”
item 是当前迭代的元素。
index 是当前元素的索引(可选)。
对象的遍历:
这里的对象感觉像是字典!
<div id="app">
<div v-for="(value,key)in fruits" :key="key">{{ key }}: {{ value }}</div>
</div>
<script>
new Vue({
el: '#app',
data: {
fruits: {
apple: '苹果',
banana: '香蕉',
orange: '橙子'
}
}
});
</script>
注意事项:
在使用 v-for 时,建议为每个元素提供一个唯一的 key 属性。key 帮助 Vue 识别哪些元素发生了变化,哪些是新增的,哪些是被移除的,从而优化渲染性能。
key 的值应该是每个元素的唯一标识符,通常可以使用数组元素的 ID。
不止可以绑定 click 事件类型
语法:
<button v-on:click="handleClick">点击</button>
<!-- 可以简写为@ -->
<button @click="handleClick">点击</button>
示例代码:
<div id="app">
<button @click="showMessage">点击我</button>
<p>{{ message }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
message: ''
},
methods: {
showMessage() {
this.message = '你点击了按钮!';
}
}
});
</script>
上述代码,当用户点击按钮时,showMessage 方法会被调用,message 会更新并显示在页面上。
事件修饰符:
阻止事件冒泡
如:<button @click.stop="doSomething">点击我</button>
阻止默认事件行为
如:
<form @submit.prevent="submitForm">
<button type="submit">提交</button>
</form>
只触发一次
如:<button @click.once="doSomething">点击我(只触发一次)</button>
仅当事件是从当前元素触发时才处理。
如:<div @click.self="doSomething">点击我(仅当前元素)</div>
在 Vue 中,prop 是父组件向子组件传递数据的一种方式,使得组件之间可以更好地进行数据交互。
作用:
数据传递:父组件可以将数据通过 props 传递给子组件。
组件复用:通过 props,同一个子组件可以在不同的父组件中接收不同的数据,从而实现复用。
语法:
在子组件中通过 props 选项定义,父组件通过 HTML 属性传递。
数据类型:可以为 props 指定类型,如 String、Number、Boolean、Array、Object、Function 等
默认值:可以为 props 提供默认值,使用 default 属性
通过 required 属性标记 pros 为必需
基本使用:
Vue.component('child-component', {
props: {
message:String, // 定义一个名为 message 的 prop,类型为 String
count: {
type:Number, // 定义一个名为 count 的 prop,类型为 Number
default:0 // 提供默认值
}
},
template: '<div>{{ message }} - {{ count }}</div>'
});
<template>
<div>
<child-component message="Hello,Vue!" :count="5"></child-component>
</div>
</template>
在上述代码中,父组件将字符串 “Hello,Vue!” 传递给 message prop,并将数字 5 传递给 count prop。
完整代码:
<!-- 父组件 -->
<template>
<div>
<child-component message="Hello,Vue!" :count="10"></child-component>
</div>
</template>
<script>
import ChildComponent from'./ChildComponent.vue';
export default {
components: {
ChildComponent
}
};
</script>
<!-- 子组件 ChildComponent.vue -->
<template>
<div>
{{ message }} - {{ count }}
</div>
</template>
<script>
export default {
props: {
message: {
type:String,
required:true // 必需
},
count: {
type:Number,
default:0 // 默认值
}
}
};
</script>
在 Vue 单文件组件(.vue 文件)中,export default 是将组件配置对象暴露给外部使用的语法,属于 JavaScript 的模块化语法(ES6 Module)。
它的作用是将当前文件(组件)的配置(如 data、props、methods 等)导出,以便其他组件或文件可以导入并使用它。
通过 export default,其他文件(例如父组件)可以通过 import 语法导入并使用这个组件
组件配置定义:
export default 后面的对象({})是 Vue 组件的核心配置,包含:
props:接收父组件传递的数据
data:定义组件内部数据
methods:定义组件方法
computed:计算属性
template 或 <template> 标签:定义组件的 HTML 结构
其他生命周期钩子(如 created、mounted 等)
components:组件注册,告诉 vue 当前组件依赖哪些子组件
代码示例:
// MyComponent.vue
<template>
<div>
<h1>{{ title }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
title: 'Hello,Vue!'
};
}
};
</script>
// ParentComponent.vue
<template>
<div>
<my-component></my-component>
</div>
</template>
<script>
import MyComponent from'./MyComponent.vue'; // 导入 MyComponent
export default {
components: {
MyComponent // 注册组件
}
};
</script>
子组件在父组件 components 中注册之后,可以直接在父组件模版中使用
当你在组件 A 的 components 中注册了子组件 B 时,组件 C 在导入组件 A 时,不需要再单独导入子组件 B。组件 A 作为一个整体已经将子组件 B 注册并暴露给了组件 C。
在 Vue.js 中,data() 是一个用于定义组件状态的函数。它的主要作用是返回一个对象,该对象包含了组件的响应式数据属性。这些数据属性可以在组件的模板中使用,并且当这些数据发生变化时,Vue 会自动更新 DOM,以反映这些变化。
具体作用:
定义响应式数据:
组件实例的状态:
每个组件实例都有独立的状态:
示例代码:
<template>
<div>
<h1>{{ message }}</h1>
<button @click="changeMessage">Change Message</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello,Vue!' // 定义响应式数据
};
},
methods: {
changeMessage() {
this.message = 'Message changed!'; // 修改响应式数据
}
}
};
</script>
在这个示例中,data() 返回了一个对象,里面包含了一个 message 属性。
在模板中,使用 {{ message }} 来显示这个属性的值。
当用户点击按钮时,changeMessage 方法会被调用,更新 message 的值。由于 message 是响应式的,Vue 会自动更新 DOM,显示新的消息。
this.$axios.get(‘/qa/report/list’, {params:params})
axios 是一个基于 Promise 的 HTTP 客户端工具,可以用于浏览器和 Node.js 环境,用于发送 HTTP 请求的主流工具库
语法:
this.$axios.get(url,config) // GET 请求
this.$axios.post(url,data,config) // POST 请求
接口返回统一形式:
{
success:true/false,
message: "xxx",
data: {
// 具体数据
}
}

项目启动及详细文档
配置路由:/src/router/index.js
{
path: "PerformTest",
components: {
PerformTest: () => import("@/components/page/Auto/PerformTest/PerformTest"),
},
},
<MenuItem name="PCweb 性能自动化"to="/PerformTest">
PCweb 性能自动化
</MenuItem>
<router-view name="PerformTest"></router-view>

5、到这里就可以在 src/components/page/Auto/PerformTest/PerformTest.vue 随便编写自己的页面内容了,可以复制下面的 demo,启动项目试一下
<template>
<div id="app">
<h1>在写了在写了</h1>
<p>敬请期待。。。</p>
</div>
</template>
<script>
export default {
data(){
return {
result: ''
}
},
mounted(){
//使用公共方法
this.util.add(0.1,0.2)
// 该页面(组建)初始化的时候 请求服务端接口拉取数据并渲染
this.$axios.get('/make').then((response)=>{
// console.log(response);
// console.log("返回的状态码:"+response.data.status);
// console.log("返回的 message:"+response.data.message);
// console.log("返回的 result:"+response.data.data);
this.result = response.data.data
}, (response)=>{
console.log("请求失败处理");
console.log(response);
})
},
methods: {
}
}
</script>
<style scoped>
</style>
编辑上节创建的 PerformTest.vue 页面,实现一些简单的接口调用
1、get 接口
进入页面就请求 get 接口,页面展示接口返回的数据
<template>
<div id='perf'>
get 接口返回数据: {{skillGroupData}}
</div>
</template>
<script>
export default {
name: "testDemo",
data(){
return {
skillGroupData:[],
}
},
// 初始化页面完成后,就执行
mounted() {
this.skillGroupDataList();
},
methods: {
// get
skillGroupDataList()
{
this.$axios.get('https://mock.corp.kuaishou.com/mock/8287/api/doc/qa/test').then(res => {
if(res.data.message==="成功") {
this.skillGroupData = res.data.data;
} else {
this.$Message.error(res.data.message);
}
})
},
},
}
</script>
<style scoped>
.unify-width
{
width:600px;
}
.unify-margin-top
{
margin-top:15px;
}
.result-button
{
width:75px;
padding:5px
}
</style>
可以使用表格的形式展示数据
<template>
<div id='perf'>
<Card:bordered="false" >
<Table stripe:data="skillGroupData"
:columns="skillGroupDataColumns" >
<template slot-scope="{ row }"slot="skillGroupId">
<span>{{ row.skillGroupId }}</span>
</template>
<template slot-scope="{ row }"slot="activeNum">
<span>{{ row.activeNum }}</span>
</template>
<template slot-scope="{ row }"slot="silentNum">
<span>{{ row.silentNum }}</span>
</template>
<template slot-scope="{ row }"slot="queueNum">
<span>{{ row.queueNum }}</span>
</template>
</Table>
</Card>
</div>
</template>
<script>
export default {
name: "testDemo",
data(){
return {
//表格数据
skillGroupData:[],
//表格列
skillGroupDataColumns:[
{
title: '技能组 ID',
slot: 'skillGroupId',
align: 'center'
},
{
title: '活跃数',
slot: 'activeNum',
align: 'center'
},
{
title: '静默数',
slot: 'silentNum',
align: 'center'
},
{
title: '排队数',
slot: 'queueNum',
align: 'center'
}
]
}
},
// 初始化页面完成后,就执行
mounted() {
this.skillGroupDataList();
},
methods: {
// get
skillGroupDataList()
{
this.$axios.get('https://mock.corp.kuaishou.com/mock/8287/api/doc/qa/test').then(res => {
if(res.data.message==="成功") {
this.skillGroupData = res.data.data;
} else {
this.$Message.error(res.data.message);
}
})
},
},
}
</script>
<style scoped>
.unify-width
{
width:600px;
}
.unify-margin-top
{
margin-top:15px;
}
.result-button
{
width:75px;
padding:5px
}
</style>
2、post 接口
获取表格中某一行的详情,即某一行的行 ID 作为 post 接口的入参数,获取详情。
表格新增一列“查看”按钮,点击触发 getskillDetail(row),处理 row 作为接口的入参 params
<template>
<div id='perf'>
<Card:bordered="false" >
<Table stripe:data="skillGroupData"
:columns="skillGroupDataColumns" >
<template slot-scope="{ row }"slot="skillGroupId">
<span>{{ row.skillGroupId }}</span>
</template>
<template slot-scope="{ row }"slot="activeNum">
<span>{{ row.activeNum }}</span>
</template>
<template slot-scope="{ row }"slot="silentNum">
<span>{{ row.silentNum }}</span>
</template>
<template slot-scope="{ row }"slot="queueNum">
<span>{{ row.queueNum }}</span>
</template>
<template slot-scope="{ row }"slot="operation">
<Button type="primary" @click="getskillDetail(row)"class="result-button">查看</Button>
</template>
</Table>
</Card>
测试 : {{detailData}} -测试
</div>
</template>
<script>
export default {
name: "testDemo",
data(){
return {
//表格数据
skillGroupData:[],
detailData:'',
//表格列
skillGroupDataColumns:[
{
title: '技能组 ID',
slot: 'skillGroupId',
align: 'center'
},
{
title: '活跃数',
slot: 'activeNum',
align: 'center'
},
{
title: '静默数',
slot: 'silentNum',
align: 'center'
},
{
title: '排队数',
slot: 'queueNum',
align: 'center'
},
{
title: '操作',
slot: 'operation',
align: 'center'
}
]
}
},
mounted() {
this.skillGroupDataList();
},
methods: {
// get
skillGroupDataList()
{
this.$axios.get('https://mock.corp.kuaishou.com/mock/8287/api/doc/qa/test').then(res => {
if(res.data.message==="成功") {
this.skillGroupData = res.data.data;
} else {
this.$Message.error(res.data.message);
}
})
},
//post
getskillDetail(row)
{
let params = {
id: row.skillGroupId,
};
this.$axios.post("https://mock.corp.kuaishou.com/mock/8287/api/doc/qa/post_test",params).then(res=> {
this.detailData = res.data.data;
}),
(res) => {
this.$Message.error("请求失败,请重试...");
};
},
},
}
</script>
<style scoped>
.unify-width
{
width:600px;
}
.unify-margin-top
{
margin-top:15px;
}
.result-button
{
width:75px;
padding:5px
}
</style>
可以使用抽屉的形式展示详情(想要什么形式展示,搜现成组件即可https://www.iviewui.com/components/drawer)
<template>
<div id='perf'>
<Card:bordered="false" >
<Table stripe:data="skillGroupData"
:columns="skillGroupDataColumns" >
<template slot-scope="{ row }"slot="skillGroupId">
<span>{{ row.skillGroupId }}</span>
</template>
<template slot-scope="{ row }"slot="activeNum">
<span>{{ row.activeNum }}</span>
</template>
<template slot-scope="{ row }"slot="silentNum">
<span>{{ row.silentNum }}</span>
</template>
<template slot-scope="{ row }"slot="queueNum">
<span>{{ row.queueNum }}</span>
</template>
<template slot-scope="{ row }"slot="operation">
<Button type="primary" @click="getskillDetail(row)"class="result-button">查看</Button>
</template>
</Table>
</Card>
<Drawer
title="详情"
v-model="detail"
width="720"
:closable="false"
:styles="styles"
>
测试 : {{detailData}} -测试
</Drawer>
</div>
</template>
<script>
export default {
name: "testDemo",
data(){
return {
//表格数据
skillGroupData:[],
detailData:'',
detail:false,
//表格列
skillGroupDataColumns:[
{
title: '技能组 ID',
slot: 'skillGroupId',
align: 'center'
},
{
title: '活跃数',
slot: 'activeNum',
align: 'center'
},
{
title: '静默数',
slot: 'silentNum',
align: 'center'
},
{
title: '排队数',
slot: 'queueNum',
align: 'center'
},
{
title: '操作',
slot: 'operation',
align: 'center'
}
]
}
},
mounted() {
this.skillGroupDataList();
},
methods: {
// get
skillGroupDataList()
{
this.$axios.get('https://mock.corp.kuaishou.com/mock/8287/api/doc/qa/test').then(res => {
if(res.data.message==="成功") {
this.skillGroupData = res.data.data;
} else {
this.$Message.error(res.data.message);
}
})
},
//post
getskillDetail(row)
{
this.detail = true;
let params = {
id: row.skillGroupId,
};
this.$axios.post("https://mock.corp.kuaishou.com/mock/8287/api/doc/qa/post_test",params).then(res=> {
this.detailData = res.data.data;
// this.$message.success(res.data.message);
this.$Message.success(str(res.data.data));
}),
(res) => {
this.$Message.error("请求失败,请重试...");
};
},
},
}
</script>
<style scoped>
.unify-width
{
width:600px;
}
.unify-margin-top
{
margin-top:15px;
}
.result-button
{
width:75px;
padding:5px
}
</style>
备注:
vue 组件三大部分:template、script、style https://www.itcast.cn/news/20211025/17304225204.shtml
View UI,即原先的 iView,是一套基于 Vue.js 的开源 UI 组件库。官方文档:https://www.iviewui.com/docs/introduce
this 详解:this 指向 window 还是 vue 实例
https://www.cnblogs.com/junjun-001/p/13181878.html
created 在模板渲染成 html 前调用,即通常初始化某些属性值,然后再渲染成视图
mounted 在模板渲染成 html 后调用,通常是初始化页面完成后,再对 html 的 dom 节点进行一些需要的操作。
这个按钮在新的页面中不需要了,因此隐藏掉就行了
数据库中有,直接展示出来就行
正整数输入框,必填
正整数输入框,必填
数据库中无需增加,页面直接计算即可:(开发消耗自然日(包含周六日)/测试消耗自然日(包含周六日))
增加几个选项:XXX,XXX,XXX

修改为:测试开始结束时间
修改为:研发时间(pd)
修改为:测试时间(pd)

修改为:备注,placeholder 修改为:请填写延期原因,测试研发比较低等

如何隐藏:通过 props 定义一个属性,在父组件调用回执表单时,把该属性赋值 true
reportDialog.vue
props: {
// ... 其他 props
hideSubmitButton: {
type:Boolean,
default:false
}
},
如上图,这边有两个按钮,分别为保存和提交审批,在前端代码中通过在 data()中定义"authorButtonList",以及在 button 中使用 v-for 属性来显示对应的按钮,如下所示
<Button
v-for="buttonText in messageForm.authorButtonList"
type="primary"
@click=handleAuthorOp(buttonText)>
{{buttonText}}
</Button>
<script>
export default {
// ...其他代码
data(){
return{
//...其他
messageForm:{
//...其他
"authorButtonList": ["保存","提交审批"]
}
}
}
}
</script>
因此要隐藏按钮,只需要添加条件判断,当 hideSubmitButton 为 true 时,将“提交审批”从列表中过滤掉
// ... existing code ...
<Button
v-for="buttonText in filteredAuthorButtonList"
type="primary"
@click=handleAuthorOp(buttonText)>
{{buttonText}}
</Button>
// ... existing code ...
<script>
export default {
// ... 其他代码
computed: {
filteredAuthorButtonList() {
if(this.hideSubmitButton) {
return this.messageForm.authorButtonList.filter(text => text!== '提交审批');
}
return this.messageForm.authorButtonList;
}
}
}
</script>
在父组件中,将子组件的 hideSubmitButton 赋值为 true 即可
<report-dialog
//其他
:hideSubmitButton="true
>
</report-dialog>
研发测试比
开发消耗自然日(包含周六日)
测试消耗自然日(包含周六日)
开发测试周期比
export default {
components: {ReportDialog},
data(){
return {
//...
{
title: '研发测试比',
key: 'frdQaPdRatio',
slot: 'frdQaPdRatio',
align: 'center'
},
{
title: '开发消耗自然日',
key: 'rdConsumeDay',
slot: 'rdConsumeDay',
align: 'center'
},
{
title: '测试消耗自然日',
key: 'qaConsumeDay',
slot: 'qaConsumeDay',
align: 'center'
},
{
title: '开发测试周期比',
key: 'rdQaCycleRatio',
slot: 'rdQaCycleRatio',
align: 'center'
},
//...
}
}
<!-- // 研发测试比 -->
<template slot-scope="{ row }"slot="frdQaPdRatio">
<span >{{ row.frdQaPdRatio }}</span>
</template>
<!-- // 研发消耗自然日 -->
<template slot-scope="{ row }"slot="rdConsumeDay">
<span >{{ row.rdConsumeDay }}</span>
</template>
<!-- // 测试消耗自然日 -->
<template slot-scope="{ row }"slot="qaConsumeDay">
<span >{{ row.qaConsumeDay }}</span>
</template>
<!-- // 研发测试周期比 -->
<template slot-scope="{ row }"slot="rdQaCycleRatio">
<span >{{ row.rdQaCycleRatio }}</span>
</template>
还不确定,可能直接前端进行计算
Object.assign(itemParam,{
"passType":passType,
"onlineRiskType":onlineRiskType,
"onlineTimestamp":onlineTimestamp,
"reportStatus":reportStatus,
"bugCnt":bugCnt,
"qa":qa,
"rd":rd,
"emergencyProject":emergencyProject,
"rdQaPdRatio":this.toDecimal2(Number(rdQaPdRatio)),
// 新的字段
"frdQaPdRatio":JSON.parse(reportList[index].memberInfo).frdQaPdRatio || '',
"rdConsumeDay":JSON.parse(reportList[index].memberInfo).rdConsumeDay || '',
"qaConsumeDay":JSON.parse(reportList[index].memberInfo).qaConsumeDay || '',
"rdQaCycleRatio":JSON.parse(reportList[index].memberInfo).rdQaCycleRatio || ''
})
/Users/caoziheng/代码相关/csc-pharos-frontend/src/components/page/index.vue 中加入菜单以及路由
<MenuItem name="测试一下"to="testCzh">测试一下</MenuItem>
<router-view name="testCzh"></router-view>
/Users/caoziheng/代码相关/csc-pharos-frontend/src/router/index.js 添加路由解析
path: "testCzh",
components: {
testReport: () =>
import("@/components/page/management/testCzh"),
},
},
修改为:测试开始结束时间
修改为:研发时间(pd)
修改为:测试时间(pd)
修改为:备注,placeholder 修改为:请填写延期原因,测试研发比较低等
上述只需要将 label 修改一下就完事了
结论:需要在后端更改新增枚举项
分析:
业务方向下拉框枚举的来源:从父组件的 props 传入到子组件
代码如下,从子组件的代码可以看出,业务方向(thirdBusinessLine)的下拉选项来自 thirdBusinessLineOptions 这个数组属性,而且是通过 props 从父组件传入
// reportDialog.vue
<FormItem label="业务方向"prop="thirdBusinessLine" :rules="ruleValidate.thirdBusinessLine" :label-width="messageLabelWidth">
<Select v-model="messageForm.thirdBusinessLine"placeholder="请选择业务线"class="col3"v-bind:disabled="messageForm.readonly">
<Option v-for="item in thirdBusinessLineOptions"v-if="item.label===messageForm.subBusinessLine"
v-bind:value="item.value" :key="item.value" :label="item.value"v-bind:readonly="messageForm.readonly" > {{item.value}}
</Option>
</Select>
</FormItem>
props: {
// ... 其他 props
'thirdBusinessLineOptions':Array, // 业务方向选项数组
}
在父组件中,通过以下方式传入
// testReport.vue
<report-dialog
:thirdBusinessLineOptions="thirdBusinessLineOptions"
// ... 其他属性
>
</report-dialog>
那么父组件中的下拉框枚举项是怎么来的呢:
是通过 getBusinessList 方法从后端接口获取的:
// testReport.vue
getBusinessList:function() {
// 调用后端接口 /qa/report/department/tree 获取数据
this.$axios.get("/qa/report/department/tree", {params: {}}).then(
body=> {
this.subBusinessLineOptions = [] // 业务线选项
this.thirdBusinessLineOptions = [] // 业务方向选项
let subDepartmentList = body.data.data.subDepartmentList[0].subDepartmentList
// 遍历接口返回的数据,构造下拉选项
for(let item of subDepartmentList) {
let subDepartmentList = item.subDepartmentList;
// 添加业务线选项
this.subBusinessLineOptions.push({"label":item.line, "value":item.line})
// 添加对应的业务方向选项
for(let subItem of subDepartmentList) {
this.thirdBusinessLineOptions.push({"label":item.line, "value":subItem.line})
}
}
}
)
}
该方法在 create 的位置,create 处的方法在组件创建时就会自动调用
created() {
this.testReportList();
this.getBusinessList(); // 组件创建时获取下拉选项
this.openReport();
},
所以整个流程是:
组件创建时调用 getBusinessList 方法
从后端接口/qa/report/department/tree 获取部门树结构数据
处理数据构造成下拉选项格式
分别存入 subBusinessLineOptions 和 thirdBusinessLineOptions
这两个数组再通过 props 传给子组件 reportDialog
最后显示在子组件即报告详情页的下拉框枚举
需求:需要在页面以及测试报告回执中都加入三个字段:开发消耗自然日、测试开发自然日、开发测试周期比
分析:
testReport.vue 是页面的 vue 组件,reportDialog.vue 是测试报告回执的组件,在 testReport.vue 中引用 reportDialog.vue 组件,相当于 reprotDialog.vue 是子组件
而测试报告数据的新增是通过在 reprotDialog.vue 中点击保存。

保存时,通过 buildReport()方法将填写的字段进行组合,点击保存时,在 savaTestReport()方法中调用后端接口"/qa/report/update"将数据传递给后端存储
buildReport:function(reportId,status)
{
// assign 方法会讲 messageForm 中的所有属性复制到 params 中
let params = Object.assign({},this.messageForm);
params.bugInfo=this.messageForm.bugJson
//params.bugInfo="{\"bugCnt\":"+this.messageForm.bugCnt+",\"P0\":" + this.messageForm.bugP0 + ",\"P1\":" + this.messageForm.bugP1 + ",\"P2\":" + this.messageForm.bugP2 + ",\"P3\":" + this.messageForm.bugP3 + "}"
params.businessLine = "综合业务质量组";
params.status = status;
params.qaTestStartTimestamp = this.messageForm.projectTestPeriod && this.formatDateStart(this.formatDate2(this.messageForm.projectTestPeriod[0]));
params.qaTestFinishTimestamp = this.messageForm.projectTestPeriod && this.formatDateEnd(this.formatDate2(this.messageForm.projectTestPeriod[1]));
params.onlineTimestamp = this.formatDateStart(this.formatDate2(this.messageForm.onlineTimestamp));
params.id = reportId;
// ##############################################################################
// 新增的三个字段
params.rdDays = this.messageForm.rdDays;
params.qaDays = this.messageForm.qaDays;
params.rdqaRatio = this.messageForm.rdqaRatio;
// ##############################################################################
console.log("测试-7-6-@1.5@--%o",params);
this.messageForm.bugJson.totalbug.P0=this.messageForm.bugP0;
this.messageForm.bugJson.totalbug.P1=this.messageForm.bugP1;
this.messageForm.bugJson.totalbug.P2=this.messageForm.bugP2;
this.messageForm.bugJson.totalbug.P3=this.messageForm.bugP3;
this.messageForm.bugJson.totalbug.P4=this.messageForm.bugP4;
console.log("测试-7-6-@2@--%o",params)
if(this.messageForm.api.trim()!=='')
{
params.api=JSON.stringify({"api":this.messageForm.api.trim().split("\n")})
}
else
{
params.api=JSON.stringify({"api": []})
}
params.bugInfo=JSON.stringify(
this.messageForm.bugJson)
params.id = reportId
if(params.onlineDelay!==1)
{
params.onlineDelayDays='';
}
if(params.rdDelay!==1)
{
params.rdDelayDays='';
}
if(params.onlineRiskType!==1)
{
params.onlineRiskContent='';
}
//成员信息
let rdList=[]
for(let index in this.messageForm.rd)
{
rdList.push({"name":this.messageForm.rd[index]})
}
let pmList=[]
for(let index in this.messageForm.pm)
{
pmList.push({"name":this.messageForm.pm[index]})
}
console.log("测试-7-6-%o",params)
let memberInfo={"pdList":pmList,"rdList":rdList,"qaList":this.messageForm.qaList,"frontList":this.messageForm.frontList,"backList":this.messageForm.backList, "rdPd":this.messageForm.rdPd, "qaPd":this.messageForm.totalQaPd, "rdQaRdRatioExceedsLimitReason":this.messageForm.rdQaRdRatioExceedsLimitReason}
console.log("测试-7-6-%o",memberInfo)
params.memberInfo=JSON.stringify(memberInfo)
// console.log("build report")
// console.log(params)
return params;
},
saveTestReport:function(status) {
//http://localhost:8877/1#/testReport/?id=42
//editStatus=1 仅保存
// editStatus=2 保存+提交审批
//第 2 期状态含义为 1:草稿,2:待审批,3:审批通过,4:审批拒绝,5:已发送,6:已删除
if(status===2)
{
//提交报告时再次确认
//提交审核的代码暂不关注
else
{
//仅保存
this.$refs['messageForm'].validate((valid) => {
if(valid) {
let reportId=0;
if(this.messageForm.id) {
reportId = this.messageForm.id;
}
console.log("测试-7-6-%o",status)
let params=this.buildReport(reportId,status)
console.log("测试-7-6-%o",params)
let checkBeforeSubmitParams=
{
"id":reportId,
"businessLine": "综合业务质量组",
"subBusinessLine":this.messageForm.subBusinessLine,
"thirdBusinessLine":this.messageForm.thirdBusinessLine,
"projectName":this.messageForm.projectName
}
this.$axios.post('/qa/report/checkBeforeSubmit',checkBeforeSubmitParams).then(
body=>
{
if(body.data.success===false)
{
this.$Message.error(body.data.message)
return
}
else
{
let params=this.buildReport(reportId,status)
//后端应返回 id
this.$axios.post("/qa/report/update",params).then(body=> {
if(body.data.success === true) {
this.$Message.success(body.data.message);
if(params.id===0)
{
//创建报告,点击保存后,关闭弹窗
this.closeModal();
}
} else {
this.$Message.error(body.data.message);
}
})
}
}
)
}
})
}
},
在 testReport.vue 的页面显示数据时,调用后端的’/qa/report/list’接口并解析数据来显示

这块逻辑一共涉及三个接口,分别是/qa/report/list、/qa/report/detail、/qa/report/update
update 将数据提交到数据库,list 和 detail 从数据库中查询数据,detail 是点击查看具体表单时调用的接口。抓取接口:
update 接口的请求载荷如下
{
"id": 1851,
"businessLine": "综合业务质量组",
"subBusinessLine": "用户体验",
"thirdBusinessLine": "客服系统",
"projectName": "热线监控预警",
"priority": "P1",
"emergencyProject": 0,
"memberInfo": "{\"pdList\":[{\"name\":\"cuina\"}],\"rdList\":[],\"qaList\":[{\"name\":\"zhangbin07\",\"pd\":\"7\"},{\"name\":\"guotengteng\",\"pd\":\"10\"}],\"frontList\":[{\"name\":\"chengshun05\",\"pd\":\"12\"}],\"backList\":[{\"name\":\"lidong13\",\"pd\":\"15\"},{\"name\":\"jizongpeng\",\"pd\":\"13\"}],\"rdPd\":\"40\",\"qaPd\":\"17\",\"rdQaRdRatioExceedsLimitReason\":\"\"}",
"qaTestStartTimestamp": 1664208000000,
"qaTestFinishTimestamp": 1665590399999,
"onlineTimestamp": 1665590400000,
"rdDelayDays": "",
"onlineDelayDays": "",
"passType": 1,
"onlineRiskType": 0,
"onlineRiskContent": "",
"changeCnt": "0",
"rejectCnt": "0",
"bugTeamUrl": "https://team.corp.kuaishou.com/task/T2752982",
"bugInfo": "{\"totalbug\":{\"P0\":\"0\",\"P1\":\"1\",\"P2\":\"19\",\"P3\":\"0\",\"P4\":\"0\",\"bugCnt\":20},\"memberbuginfo\":{\"zhangbin07\":{\"P0\":0,\"P1\":0,\"P2\":3,\"P3\":0,\"P4\":0},\"guotengteng\":{\"P0\":0,\"P1\":1,\"P2\":16,\"P3\":0,\"P4\":0}}}",
"testPoint": "1、热线监控看板/预警管理页面交互功能\n2、热线监控指标计算",
"onlineFunction": "1、数据维度:热线实时运营看板支持业务线、技能组、部组、员工四个维度数据监控,可不同维度间指标下钻 \n2、指标展示:支持查看核心指标、接起明细、状态明细三类指标,并按照现场排查问题方法设计指标展示顺序\n3、指标预警:支持分业务线进行指标目标值、正常值、预警值、异常值设置,并在看板处分颜色预警展示\n4、指标放大镜:关键指标支持放大镜展示时段数据趋势图,相关指标可联动查看",
"testCaseUrl": "",
"xmindFileUrl": "reporter_xmd_IyHLbaMdIWsrJMgi.xmind",
"testcaseCnt": "0",
"relatedDocuments": "prd :https://docs.corp.kuaishou.com/k/home/VURIph0PrMEg/fcABKvNc3T1qBAJaWk1Jrko2D\n 提测单:https://docs.corp.kuaishou.com/k/home/VQ3jHiClWdhM/fcABjsNYbyiyol1seQiFBwxUi\n 指标说明:https://docs.corp.kuaishou.com/t/home/VYxy3ArL3i1M/fcACGrFYr6jEFuKoeC2BPX2lF",
"note": "测试总结:https://docs.corp.kuaishou.com/k/home/VCw2ArVpEwy4/fcADtuIlMswr-Wn2GeVBM8viX",
"creator": "郭腾腾",
"createTimestamp": 1665729868000,
"updateTimestamp": 1665746435000,
"preEmail": "guotengteng",
"status": 1,
"approvalPermission": 2,
"api": "{\"api\":[]}",
"projectTestPeriod": [
"2022-09-26T16:00:00.000Z",
"2022-10-11T16:00:00.000Z"
],
"bugP0": "0",
"bugP1": "1",
"bugP2": "19",
"bugP3": "0",
"bugP4": "0",
"bugCnt": "20",
"bugJson": {
"totalbug": {
"P0": "0",
"P1": "1",
"P2": "19",
"P3": "0",
"P4": "0",
"bugCnt": 20
},
"memberbuginfo": {
"zhangbin07": {
"P0": 0,
"P1": 0,
"P2": 3,
"P3": 0,
"P4": 0
},
"guotengteng": {
"P0": 0,
"P1": 1,
"P2": 16,
"P3": 0,
"P4": 0
}
}
},
"onlineDelay": 0,
"rdDelay": 0,
"pm": [
"cuina"
],
"rd": [
],
"qaList": [
{
"name": "zhangbin07",
"pd": "7"
},
{
"name": "guotengteng",
"pd": "10"
}
],
"frontList": [
{
"name": "chengshun05",
"pd": "12"
}
],
"backList": [
{
"name": "lidong13",
"pd": "15"
},
{
"name": "jizongpeng",
"pd": "13"
}
],
"rdPd": "40",
"totalQaPd": "17",
"rdQaRdRatioExceedsLimitReason": "",
"readonly": false,
"testCaseUploadMethod": [
"xmind"
],
"userList": [
],
"approvalButtonList": [
],
"authorButtonList": [
"保存"
],
"loading2": false
}
detail 的响应为:
"detail": {
"id":1851,
"businessLine": "综合业务质量组",
"subBusinessLine": "用户体验",
"thirdBusinessLine": "客服系统",
"projectName": "热线监控预警",
"priority": "P1",
"emergencyProject":0,
"memberInfo": "{\"qaPd\": \"17\", \"rdPd\": \"40\", \"pdList\": [{\"name\": \"cuina\"}], \"qaList\": [{\"pd\": \"7\", \"name\": \"zhangbin07\"}, {\"pd\": \"10\", \"name\": \"guotengteng\"}], \"rdList\": [], \"backList\": [{\"pd\": \"15\", \"name\": \"lidong13\"}, {\"pd\": \"13\", \"name\": \"jizongpeng\"}], \"frontList\": [{\"pd\": \"12\", \"name\": \"chengshun05\"}], \"rdQaPdRatio\":2.35, \"rdQaRdRatioExceedsLimitReason\": \"\"}",
"qaTestStartTimestamp":1664208000000,
"qaTestFinishTimestamp":1665590399999,
"onlineTimestamp":1665590400000,
"rdDelayDays":0.0,
"onlineDelayDays":0.0,
"passType":1,
"onlineRiskType":0,
"onlineRiskContent": "",
"changeCnt":0,
"rejectCnt":0,
"bugTeamUrl": "https://team.corp.kuaishou.com/task/T2752982",
"bugInfo": "{\"totalbug\": {\"P0\": \"0\", \"P1\": \"1\", \"P2\": \"19\", \"P3\": \"0\", \"P4\": \"0\", \"bugCnt\":20}, \"memberbuginfo\": {\"zhangbin07\": {\"P0\":0, \"P1\":0, \"P2\":3, \"P3\":0, \"P4\":0}, \"guotengteng\": {\"P0\":0, \"P1\":1, \"P2\":16, \"P3\":0, \"P4\":0}}}",
"testPoint": "1、热线监控看板/预警管理页面交互功能\n2、热线监控指标计算",
"onlineFunction": "1、数据维度:热线实时运营看板支持业务线、技能组、部组、员工四个维度数据监控,可不同维度间指标下钻 \n2、指标展示:支持查看核心指标、接起明细、状态明细三类指标,并按照现场排查问题方法设计指标展示顺序\n3、指标预警:支持分业务线进行指标目标值、正常值、预警值、异常值设置,并在看板处分颜色预警展示\n4、指标放大镜:关键指标支持放大镜展示时段数据趋势图,相关指标可联动查看",
"testCaseUrl": "",
"xmindFileUrl": "reporter_xmd_IyHLbaMdIWsrJMgi.xmind",
"testcaseCnt":0,
"relatedDocuments": "prd:https://docs.corp.kuaishou.com/k/home/VURIph0PrMEg/fcABKvNc3T1qBAJaWk1Jrko2D\n 提测单:https://docs.corp.kuaishou.com/k/home/VQ3jHfcABjsNYbyiyol1seQiFBwxUi\n 指标说明:https://docs.corp.kuaishou.com/t/home/VYxy3ArL3i1M/fcACGrFYr6jEFuKoeC2BPX2lF",
"note": "测试总结:https://docs.corp.kuaishou.com/k/home/VCw2ArVpEwy4/fcADtuIlMswr-Wn2GeVBM8viX",
"creator": "曹子亨",
"createTimestamp":1665729868000,
"updateTimestamp":1743563822000,
"preEmail": "guotengteng",
"status":1,
"approvalPermission":2,
"api": "{\"api\": []}"
}
list 的响应,只列举了其中一条 reportlist
"reportList": [
{
"id":1851,
"subBusinessLine": "用户体验",
"thirdBusinessLine": "客服系统",
"projectName": "热线监控预警",
"emergencyProject":0,
"onlineTimestamp":1665590400000,
"memberInfo": "{\"qaPd\": \"17\", \"rdPd\": \"40\", \"pdList\": [{\"name\": \"cuina\"}], \"qaList\": [{\"pd\": \"7\", \"name\": \"zhangbin07\"}, {\"pd\": \"10\", \"name\": \"guotengteng\"}], \"rdList\": [], \"backList\": [{\"pd\": \"15\", \"name\": \"lidong13\"}, {\"pd\": \"13\", \"name\": \"jizongpeng\"}], \"frontList\": [{\"pd\": \"12\", \"name\": \"chengshun05\"}], \"rdQaPdRatio\":2.35, \"rdQaRdRatioExceedsLimitReason\": \"\"}",
"passType":1,
"onlineRiskType":0,
"bugInfo": "{\"totalbug\": {\"P0\": \"0\", \"P1\": \"1\", \"P2\": \"19\", \"P3\": \"0\", \"P4\": \"0\", \"bugCnt\":20}, \"memberbuginfo\": {\"zhangbin07\": {\"P0\":0, \"P1\":0, \"P2\":3, \"P3\":0, \"P4\":0}, \"guotengteng\": {\"P0\":0, \"P1\":1, \"P2\":16, \"P3\":0, \"P4\":0}}}",
"status": "creator": "曹子亨",
"updateTimestamp":1743563822000,
"approvalPermission":0
},
//其他记录
]
由于不改后端接口返回字段以及增加数据库的字段,因此必需在现有逻辑中进行修改,增加开发消耗自然日、测试消耗自然日和开发测试自然周期比三个字段
那么就只能从 menberInfo 字段入手(或 bugInfo 其实也行)。update 时,将三个新字段的数据加入 menberinfo 中,list 时,解析出 menberInfo 中的新加字段,detail 解析也得加

在后端代码中,搜索该接口,后端代码如下:
@Slf4j
@RestController
@RequestMapping("/api/qa/report/department")
public class DepartmentController {
@Autowired
DepartmentService departmentService;
/**
* 获取部门树
*/
@GetMapping("/tree")
public Response getDepartmentTree() {
List<DepartmentEntity> list = departmentService.getDepartmentEntityTree();
Map<String,Object> map = new HashMap<>();
map.put("subDepartmentList",list);
map.put("total",list.size());
return Response.yes(map);
}
}
回答:并不是接口的问题,当我重新刷新页面之后抓到了该接口,接口调用 testReport.vue 中,因此当该页面创建时就立马调用了该接口,而不是等打开报告详情页时再调用
问题分析:
访问后端接口时,打开开发者工具,接口请求状态为待处理,点开详情,发现请求的地址是本地的 8877 端口


在启动后端项目中,发现 http 服务器监听的是本地的 8888 端口

因此需要将前端接口请求地址改为本地的 8888 端口
在 vue.config.js 中修改 proxy,在 vue.proxy.config.js 中将/api 的访问地址修改,发现其原本就是访问本地 8888 端口


查看 axios 的 baseURL,发现被定义为本地的 8877 端口,将其修改为 8888

修改完之后也确实是访问 8888 端口,但是由于网络认证限制,访问并没有成功
