没有理想的人不伤心

VUE 基础与实战讲解

2025/09/10
1
0

image.png

1 vue.js 项目的文件组织

1.1 项目目录结构

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       # 项目依赖和脚本配置

生产环境下的项目目录结构与上面的差不多

image.png|400

1.2 核心文件

  1. main.js

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 元素
  1. App.vue

App.vue 是整个应用的根组件,通常包含全局布局(如导航栏、页脚)和路由占位符。

基础代码:

<template>
  <div id="app">
    <nav>...</nav>
    <router-view />  <!-- 路由页面会渲染在这里 -->
    <footer>...</footer>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style scoped>
/* 全局或局部样式 */
</style>
  1. components/和 views/
  • components/:存放可复用的子组件(如按钮、卡片等)。

  • views/:存放页面级组件(如 HomeView.vue,AboutView.vue)。

  1. 路由 router/index.js

配置页面路由和对应组件的关系

基础代码:

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
  1. 状态管理 store/index.js 或 vuex/store.js

集中管理全局状态,如用户的登录信息

基础代码:

import { createStore } from'vuex'

export default createStore({
  state: {
    count:0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment')
      },1000)
    }
  }
})
  1. 静态资源 assers/和 public/
  • assets/:图片、样式等资源,通过相对路径引用:

  •   <img src="@/assets/logo.png" />
    
    
  • public/:直接复制到构建结果的静态文件,通过绝对路径引用:

  •   <img src="/public-image.png" />
      
    
    

1.3 文件调用流程

  1. 启动应用:main.js 初始化 Vue 实例,挂载 App.vue 到 DOM。

  2. 路由匹配:用户访问 URL 时,router 根据路径找到对应的页面组件(如 HomeView.vue)。

  3. 组件渲染:页面组件通过 <router-view> 渲染,内部可嵌套子组件(components/)。

  4. 状态管理:全局状态通过 store 共享,组件通过 this.$store 或 useStore 访问。

  5. 数据流动:父子组件通过 props(父传子)和 $emit(子传父)通信,跨组件通过 store 共享状态。

2 vue 组件文件内容

vue 文件分为 template 部分和 script 部分、style 部分

2.1 template

<template>
  <!-- 这里是组件的 HTML 结构 -->
</template>

这是组件的模板部分,定义组件的 HTML 结构

  • 使用 Vue 的特殊指令如 v-if 、 v-for 、 v-model 等

  • 只能有一个根元素

2.2 script

<script>
// 导入其他组件或模块
import ComponentA from'./ComponentA.vue'

export default {
  // 组件配置项
  name: '组件名',
  
  // 注册子组件
  components: {
    ComponentA
  },
  
  // 组件数据
  data() {
    return {
      message: 'Hello'
    }
  },
  
  // 方法
  methods: {
    handleClick() {
      // 处理逻辑
    }
  },
  
  // 生命周期钩子
  created() {
    // 组件创建时执行
  }
}
</script>

主要配置项说明:

  • import 语句

用于导入其他组件、工具函数或资源

  • 组件配置

name: 组件名称

components: 注册子组件

props: 接收父组件传递的数据

data() : 组件的数据

methods: 组件的方法

computed: 计算属性

watch: 监听数据变化

  • 生命周期钩子

created() : 组件创建完成

mounted() : 组件挂载到 DOM

updated() : 组件更新完成

destroyed() : 组件销毁

2.3 style

<style>
/* CSS 样式 */
</style>

3 v-指令

在 Vue.js 中,v- 前缀的语法被称为 指令 (Directives),它们是特殊的 HTML 属性,用于为 DOM 元素添加动态行为。

核心作用:

  1. 数据绑定:将数据动态绑定到 DOM(如 v-bind,v-model)

  2. 条件渲染:控制元素的显示/隐藏(如 v-if,v-show)

  3. 循环渲染:基于数组或对象渲染列表(如 v-for)

  4. 事件监听:监听 DOM 事件(如 v-on)

  5. 自定义行为:通过自定义指令扩展功能

3.1 数据绑定

  • 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 修饰符:

  • lazy:在 change 事件后更新数据(而不是 input 事件)。

<input v-model.lazy="message">

  • number:将输入值转换为数字。

<input v-model.number="age">

  • trim:去除输入值的首尾空格。

<input v-model.trim="message">

3.2 条件渲染

  • v-if / v-else-if / v-else:条件渲染(销毁/重建元素)

示例代码:

<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>
  • v-show:也是用于条件渲染,但它通过 CSS 的 display 属性来控制元素的显示与隐藏,而不是将元素从 DOM 中移除。

示例代码:

    <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 控制显示与隐藏。

  • 适合用于频繁切换的场景,因为切换的性能开销更小。

3.3 循环渲染

  • v-for:用于在数组或对象上进行迭代,生成多个元素。

示例代码:

    <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。

3.4 事件监听

  • v-on:监听 DOM 事件,用于绑定 DOM 事件到 Vue 实例的方法上,使得开发者能够响应用户的交互行为

不止可以绑定 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 会更新并显示在页面上。

事件修饰符:

  • .stop

阻止事件冒泡

如:<button @click.stop="doSomething">点击我</button>

  • .prevent

阻止默认事件行为

如:

<form @submit.prevent="submitForm">
  <button type="submit">提交</button>
</form>
  • .once

只触发一次

如:<button @click.once="doSomething">点击我(只触发一次)</button>

  • .self

仅当事件是从当前元素触发时才处理。

如:<div @click.self="doSomething">点击我(仅当前元素)</div>

4 prop(property)

在 Vue 中,prop 是父组件向子组件传递数据的一种方式,使得组件之间可以更好地进行数据交互。

作用:

  1. 数据传递:父组件可以将数据通过 props 传递给子组件。

  2. 组件复用:通过 props,同一个子组件可以在不同的父组件中接收不同的数据,从而实现复用。

语法:

  • 在子组件中通过 props 选项定义,父组件通过 HTML 属性传递。

  • 数据类型:可以为 props 指定类型,如 String、Number、Boolean、Array、Object、Function 等

  • 默认值:可以为 props 提供默认值,使用 default 属性

  • 通过 required 属性标记 pros 为必需

基本使用:

  1. 在子组件中定义 Props:在子组件中,使用 props 选项来定义可以接收的属性。
Vue.component('child-component', {
  props: {
    message:String, // 定义一个名为 message 的 prop,类型为 String
    count: {
      type:Number, // 定义一个名为 count 的 prop,类型为 Number
      default:0    // 提供默认值
    }
  },
  template: '<div>{{ message }} - {{ count }}</div>'
});
  1. 在父组件中传递 Props:在父组件的模板中,使用子组件时,可以通过属性的形式传递数据。
<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>

5 export default

在 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 中注册之后,可以直接在父组件模版中使用 这样的标签来引用 MyComponent。Vue 会识别这个标签并渲染相应的子组件。

当你在组件 A 的 components 中注册了子组件 B 时,组件 C 在导入组件 A 时,不需要再单独导入子组件 B。组件 A 作为一个整体已经将子组件 B 注册并暴露给了组件 C。

6 data()

在 Vue.js 中,data() 是一个用于定义组件状态的函数。它的主要作用是返回一个对象,该对象包含了组件的响应式数据属性。这些数据属性可以在组件的模板中使用,并且当这些数据发生变化时,Vue 会自动更新 DOM,以反映这些变化。

具体作用:

  1. 定义响应式数据:

    • data() 函数返回的对象中的属性会被 Vue 转换为响应式属性。这意味着,当这些属性的值发生变化时,Vue 会自动检测到变化并更新相关的视图。
  2. 组件实例的状态:

    • data() 中定义的数据通常用于存储组件的状态,例如用户输入、请求返回的数据、计数器的值等。
  3. 每个组件实例都有独立的状态:

    • data() 函数在每个组件实例创建时被调用,因此每个组件实例都有自己独立的 data 对象。这避免了不同组件实例之间的数据相互干扰。

示例代码:

<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,显示新的消息。

7 接口调用

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: {
    // 具体数据
  }
}

简单写个页面

一:在导航栏中创建一个页面

image.png|200

  1. 项目启动及详细文档

  2. 配置路由:/src/router/index.js

        {
          path: "PerformTest",
          components: {
            PerformTest: () => import("@/components/page/Auto/PerformTest/PerformTest"),
          },
        },
  1. 路由入口:/src/components/page/index.vue
<MenuItem name="PCweb 性能自动化"to="/PerformTest">
PCweb 性能自动化
</MenuItem>
  1. 路由出口:/src/components/page/index.vue
<router-view name="PerformTest"></router-view>
  1. 可以根据导航栏的页面位置规划代码位置,放在/src/components/page 里面,如创建文件 Auto/PerformTest/PerformTest.vue

image.png|300

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>

二、使用 axios 调用接口

编辑上节创建的 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>

备注:

  1. vue 组件三大部分:template、script、style https://www.itcast.cn/news/20211025/17304225204.shtml

  2. View UI,即原先的 iView,是一套基于 Vue.js 的开源 UI 组件库。官方文档:https://www.iviewui.com/docs/introduce

  3. this 详解:this 指向 window 还是 vue 实例
    https://www.cnblogs.com/junjun-001/p/13181878.html

  4. Vue.js 中 created( ) 与 mounted( )的区别

  5. created 在模板渲染成 html 前调用,即通常初始化某些属性值,然后再渲染成视图

  6. mounted 在模板渲染成 html 后调用,通常是初始化页面完成后,再对 html 的 dom 节点进行一些需要的操作。

前端页面开发需求

1 需求分解

1.1 隐藏提交审批按钮

这个按钮在新的页面中不需要了,因此隐藏掉就行了
image.png

1.2 新增字段展示

  • 研发测试比

数据库中有,直接展示出来就行

  • 开发消耗自然日(包含周六日)

正整数输入框,必填

  • 测试消耗自然日(包含周六日)

正整数输入框,必填

  • 开发测试周期比

数据库中无需增加,页面直接计算即可:(开发消耗自然日(包含周六日)/测试消耗自然日(包含周六日))

1.3 报告详情页更改

  • 业务方向

增加几个选项:XXX,XXX,XXX

image.png|350

  • 测试时间

修改为:测试开始结束时间

  • 开发总人天

修改为:研发时间(pd)

  • 测试总人天

修改为:测试时间(pd)

image.png

  • 其他

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

image.png|700

2 页面开发

2.1 隐藏提交审批按钮

如何隐藏:通过 props 定义一个属性,在父组件调用回执表单时,把该属性赋值 true

reportDialog.vue

  • props 添加 hideSubmitButton 属性,默认值为 false 不隐藏
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>

2.2 新增字段展示

  • 研发测试比

  • 开发消耗自然日(包含周六日)

  • 测试消耗自然日(包含周六日)

  • 开发测试周期比

  1. 在 data()中加入对应字段
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'
          },
				//...
    }
}
  1. 在前端模版也加入
<!-- // 研发测试比 -->
<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>
  1. 在 testReportList 方法中加入后端接口的相应字段解析

还不确定,可能直接前端进行计算

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 || ''
})
  1. 添加该组件对应的前端菜单

/Users/caoziheng/代码相关/csc-pharos-frontend/src/components/page/index.vue 中加入菜单以及路由

<MenuItem name="测试一下"to="testCzh">测试一下</MenuItem>

<router-view name="testCzh"></router-view>
  1. 在跟路由下加

/Users/caoziheng/代码相关/csc-pharos-frontend/src/router/index.js 添加路由解析

path: "testCzh",
  components: {
    testReport: () =>
    import("@/components/page/management/testCzh"),
  },
},

2.3 报告详情页更改

  • 测试时间

修改为:测试开始结束时间

  • 开发总人天

修改为:研发时间(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();
},

所以整个流程是:

  1. 组件创建时调用 getBusinessList 方法

  2. 从后端接口/qa/report/department/tree 获取部门树结构数据

  3. 处理数据构造成下拉选项格式

  4. 分别存入 subBusinessLineOptions 和 thirdBusinessLineOptions

  5. 这两个数组再通过 props 传给子组件 reportDialog

  6. 最后显示在子组件即报告详情页的下拉框枚举

2.4 JSON 串构造和解析

需求:需要在页面以及测试报告回执中都加入三个字段:开发消耗自然日、测试开发自然日、开发测试周期比

分析:

testReport.vue 是页面的 vue 组件,reportDialog.vue 是测试报告回执的组件,在 testReport.vue 中引用 reportDialog.vue 组件,相当于 reprotDialog.vue 是子组件

而测试报告数据的新增是通过在 reprotDialog.vue 中点击保存。

image.png

保存时,通过 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’接口并解析数据来显示

image.png

这块逻辑一共涉及三个接口,分别是/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 解析也得加

疑问:

  1. 从后端接口/qa/report/department/tree 获取部门树结构数据,但是我在页面抓取接口时,并没有抓取到这个接口

image.png

在后端代码中,搜索该接口,后端代码如下:

@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 中,因此当该页面创建时就立马调用了该接口,而不是等打开报告详情页时再调用

  1. 后端工程显示启动成功,但是前端页面在访问后端接口时并不能访问成功

问题分析:

访问后端接口时,打开开发者工具,接口请求状态为待处理,点开详情,发现请求的地址是本地的 8877 端口

image.png

image.png

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

image.png

因此需要将前端接口请求地址改为本地的 8888 端口

在 vue.config.js 中修改 proxy,在 vue.proxy.config.js 中将/api 的访问地址修改,发现其原本就是访问本地 8888 端口

image.png

image.png

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

image.png

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

image.png