自动化测试平台开发(八):接口测试 - vue前端请求接口渲染页面
本篇以接口管理为例,记录前端vue请求后端接口,获取接口表数据,分页展示接口列表。并实现接口新增、编辑、更新等功能。
前端基于开源vue-admin-template
1. 页面路由
router/index.js
1 import Vue from 'vue' 2 import Router from 'vue-router' 3 4 Vue.use(Router) 5 6 /* Layout */ 7 import Layout from '@/layout' 8 // import fa from 'element-ui/src/locale/lang/fa' 9 10 /** 11 * Note: sub-menu only appear when route children.length >= 1 12 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html 13 * 14 * hidden: true if set true, item will not show in the sidebar(default is false) 15 * alwaysShow: true if set true, will always show the root menu 16 * if not set alwaysShow, when item has more than one children route, 17 * it will becomes nested mode, otherwise not show the root menu 18 * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb 19 * name:'router-name' the name is used by <keep-alive> (must set!!!) 20 * meta : { 21 roles: ['admin','editor'] control the page roles (you can set multiple roles) 22 title: 'title' the name show in sidebar and breadcrumb (recommend set) 23 icon: 'svg-name'/'el-icon-x' the icon show in the sidebar 24 breadcrumb: false if set false, the item will hidden in breadcrumb(default is true) 25 activeMenu: '/example/list' if set path, the sidebar will highlight the path you set 26 } 27 */ 28 29 /** 30 * constantRoutes 31 * a base page that does not have permission requirements 32 * all roles can be accessed 33 */ 34 export const constantRoutes = [ 35 { 36 path: '/login', 37 component: () => import('@/views/login/index'), 38 hidden: true 39 }, 40 41 { 42 path: '/404', 43 component: () => import('@/views/404'), 44 hidden: true 45 }, 46 { 47 path: '/', 48 component: Layout, 49 redirect: '/dashboard', 50 hidden: true, 51 children: [ 52 { 53 path: 'dashboard', 54 name: 'Dashboard', 55 hidden: true, 56 component: () => import('@/views/dashboard'), 57 meta: { title: 'Dashboard', icon: 'dashboard' } 58 } 59 ] 60 }, 61 // 仪表盘 62 { 63 path: '/api/dashboard', 64 component: Layout, 65 // redirect: '/', 66 name: 'Dashboard', 67 meta: { title: 'Dashboard', icon: '' } 68 }, 69 // 工单分析 70 { 71 path: '/api/order', 72 component: Layout, 73 redirect: '/api/order/list', 74 name: 'Orders', 75 meta: { title: '工单分析', icon: 'form' }, 76 children: [ 77 { 78 path: 'list', 79 name: 'order', 80 component: () => import('@/views/orders/OrderList'), 81 meta: { title: '工单列表', icon: 'table' } 82 }, 83 { 84 path: 'statistic', 85 name: 'Statistics', 86 component: () => import('@/views/orders/Statistics'), 87 meta: { title: '数据统计', icon: 'blue-bar' } 88 }, 89 { 90 path: 'analysis', 91 name: 'Analysis', 92 component: () => import('@/views/orders/Analysis'), 93 meta: { title: '趋势分析', icon: 'blue-line' } 94 } 95 ] 96 }, 97 // 接口测试 98 { 99 path: '/api/apiTest', 100 redirect: '/api/apiTest/workbench', 101 component: Layout, 102 name: '接口测试', 103 meta: { title: '接口测试', icon: 'el-icon-cpu' }, 104 children: [ 105 { 106 path: 'workbench', 107 component: () => import('@/views/apiTest/workbench'), 108 name: '工作台', 109 meta: { title: '工作台', icon: 'el-icon-s-platform' } 110 }, 111 { 112 path: 'global', 113 component: () => import('@/views/apiTest/globalMgr'), 114 name: '全局配置', 115 meta: { title: '全局配置', icon: 'el-icon-setting' } 116 }, 117 { 118 path: 'projectMgr', 119 name: '项目管理', 120 component: () => import('@/views/apiTest/projectMgr'), 121 meta: { title: '项目管理', icon: 'tree' } 122 }, 123 { 124 path: 'projectMgr/project_id=:project_id', 125 component: () => import('@/views/apiTest/projectMgr/projectDetail'), 126 hidden: true, 127 name: '项目详情', 128 meta: { title: '项目详情', icon: 'table' } 129 }, 130 { 131 path: 'apiMgr', 132 name: '接口管理', 133 component: () => import('@/views/apiTest/apiMgr'), 134 meta: { title: '接口管理', icon: 'el-icon-s-ticket' }, 135 children: [ 136 { 137 path: 'api_id=:api_id', 138 component: () => import('@/views/apiTest/apiMgr/apiDetail'), 139 hidden: true, 140 name: '接口详情1', 141 meta: { title: '接口详情1' } 142 } 143 ] 144 }, 145 { 146 path: 'apiMgr/api_id=:api_id', 147 component: () => import('@/views/apiTest/apiMgr/apiDetail'), 148 hidden: true, 149 name: '接口详情', 150 meta: { title: '接口详情', icon: 'table', keepAlive: true } 151 }, 152 { 153 path: 'caseMgr', 154 component: () => import('@/views/apiTest/caseMgr'), 155 name: '用例管理', 156 meta: { title: '用例管理', icon: 'el-icon-suitcase', keepAlive: true } 157 }, 158 { 159 path: 'caseMgr/case_id=:case_id', 160 component: () => import('@/views/apiTest/caseMgr/caseDetail'), 161 hidden: true, 162 name: '用例详情', 163 meta: { title: '用例详情', icon: 'table', keepAlive: true } 164 }, 165 { 166 path: 'testReport', 167 name: '历史报告', 168 component: () => import('@/views/apiTest/reportMgr'), 169 meta: { title: '历史报告', icon: 'el-icon-notebook-1' }, 170 children: [ 171 { 172 hidden: true, 173 path: 'pytest_html/report_id=:report_id', 174 component: () => import('@/views/apiTest/reportMgr/reportDetail/PytestHtmlReport'), 175 name: 'PytestHtml', 176 meta: { title: 'PytestHtml' } 177 }, 178 { 179 hidden: true, 180 path: 'allure_html/report_id=:report_id', 181 component: () => import('@/views/apiTest/reportMgr/reportDetail/AllureHtmlReport'), 182 name: 'AllureHtml', 183 meta: { title: 'AllureHtml' } 184 }, 185 { 186 hidden: true, 187 path: 'test_logs/report_id=:report_id', 188 component: () => import('@/views/apiTest/reportMgr/reportDetail/TestLogs'), 189 name: 'TestLogs', 190 meta: { title: 'TestLogs' } 191 } 192 ] 193 }, 194 { 195 path: 'dataMgr', 196 name: '导入导出', 197 component: () => import('@/views/apiTest/dataMgr/index'), 198 meta: { title: '数据管理', icon: 'el-icon-upload' } 199 }, 200 { 201 path: 'taskMgr', 202 name: '任务管理', 203 component: () => import('@/views/apiTest/taskMgr/index'), 204 meta: { title: '任务管理', icon: 'el-icon-s-promotion' } 205 }, 206 { 207 path: '/api/apiTest/statisticalAnalysis', 208 component: () => import('@/views/apiTest/statisticalAnalysis'), 209 name: '统计分析', 210 meta: { title: '统计分析', icon: 'el-icon-s-data' }, 211 children: [ 212 { 213 path: 'summary', 214 component: () => import('@/views/apiTest/statisticalAnalysis/summary/index'), 215 name: '概览', 216 meta: { title: '概览' } 217 }, 218 { 219 path: 'api', 220 component: () => import('@/views/apiTest/statisticalAnalysis/apiAnalysis/index'), 221 name: '接口分析', 222 meta: { title: '接口分析' } 223 }, 224 { 225 path: 'case', 226 component: () => import('@/views/apiTest/statisticalAnalysis/caseAnalysis/index'), 227 name: '用例分析', 228 meta: { title: '用例分析' } 229 }, 230 { 231 path: 'result', 232 component: () => import('@/views/apiTest/statisticalAnalysis/resultAnalysis/index'), 233 name: '结果分析', 234 meta: { title: '结果分析' } 235 }, 236 { 237 path: 'work', 238 component: () => import('@/views/apiTest/statisticalAnalysis/workAnalysis/index'), 239 name: '进度分析', 240 meta: { title: '进度分析' } 241 } 242 ] 243 }, 244 { 245 path: '/api/apiTest/help', 246 name: '帮助文档', 247 component: () => import('@/views/apiTest/help'), 248 meta: { title: '帮助', icon: 'tree' }, 249 children: [ 250 { 251 path: 'validate_rules', 252 component: () => import('@/views/apiTest/help/BuiltinComparators'), 253 name: '数据校验器', 254 meta: { title: '数据校验器' } 255 }, 256 { 257 path: 'builtin_functions', 258 component: () => import('@/views/apiTest/help/BuiltinFunctions'), 259 name: '内建函数', 260 meta: { title: '内建函数' } 261 }, 262 { 263 path: 'customized_functions', 264 component: () => import('@/views/apiTest/help/CustomizedFunctions'), 265 name: '接口封装', 266 meta: { title: '接口封装' } 267 } 268 ] 269 } 270 ] 271 }, 272 // 系统管理 273 { 274 path: '/api/systemManage', 275 component: Layout, 276 name: '系统管理', 277 meta: { title: '系统管理', icon: 'el-icon-setting' }, 278 children: [ 279 { 280 path: 'organization', 281 component: () => import('@/views/system-manage/organization'), 282 name: '团队组织', 283 meta: { title: '团队组织', icon: 'peoples' }, 284 // hidden: true, 285 children: [ 286 { 287 path: 'department', 288 component: () => import('@/views/system-manage/organization/department'), 289 name: '部门管理', 290 meta: { title: '部门管理', icon: 'tree-table' } 291 }, 292 { 293 path: 'user', 294 component: () => import('@/views/system-manage/organization/user'), 295 name: '用户管理', 296 meta: { title: '用户管理', icon: 'el-icon-user' } 297 }, 298 { 299 path: 'permission', 300 component: () => import('@/views/system-manage/config-center/Permission'), 301 name: '权限管理', 302 meta: { title: '权限管理', icon: 'el-icon-lock' } 303 } 304 ] 305 }, 306 { 307 path: 'configCenter', 308 component: () => import('@/views/system-manage/config-center'), 309 name: '配置中心', 310 meta: { title: '配置中心', icon: 'el-icon-setting' }, 311 hidden: true, 312 children: [ 313 { 314 path: 'permission', 315 component: () => import('@/views/system-manage/config-center/Permission'), 316 name: '权限管理', 317 meta: { title: '权限管理', icon: 'el-icon-lock' } 318 }, 319 { 320 path: 'theme', 321 component: () => import('@/views/system-manage/config-center/Theme'), 322 name: '主题配置', 323 meta: { title: '主题配置', icon: 'el-icon-s-open' } 324 } 325 ] 326 }, 327 { 328 path: 'systemLogs', 329 component: () => import('@/views/system-manage/system-logs'), 330 name: '系统日志', 331 meta: { title: '系统日志', icon: 'el-icon-notebook-2' }, 332 children: [ 333 { 334 path: 'message', 335 component: () => import('@/views/system-manage/system-logs/Message'), 336 name: 'message', 337 meta: { title: 'message', icon: 'el-icon-notebook-2' } 338 }, 339 { 340 path: 'logs/api_test', 341 component: () => import('@/views/system-manage/system-logs/ApiTest'), 342 name: 'api_test', 343 meta: { title: 'api_test', icon: 'el-icon-notebook-2' } 344 }, 345 { 346 path: 'logs/error', 347 component: () => import('@/views/system-manage/system-logs/Error'), 348 name: 'error', 349 meta: { title: 'error', icon: 'el-icon-notebook-2' } 350 } 351 ] 352 } 353 ] 354 }, 355 // 404 page must be placed at the end !!! 356 { path: '*', redirect: '/404', hidden: true } 357 ] 358 359 360 const createRouter = () => new Router({ 361 // mode: 'history', // require service support 362 scrollBehavior: () => ({ y: 0 }), 363 routes: constantRoutes 364 }) 365 366 const router = createRouter() 367 368 // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 369 export function resetRouter() { 370 const newRouter = createRouter() 371 router.matcher = newRouter.matcher // reset router 372 } 373 374 export default router
2. 从0到1实现一个页面
示例:全局环境配置页面
- src/api/新建global_env.js并添加后端接口调用方法
1 import request from '@/utils/request' 2 3 // 获取全局ENV(环境配置)配置列表 4 export function getGlobalEnvList(params) { 5 return request({ 6 url: '/api_test/global/env/list', 7 method: 'get', 8 params: params 9 }) 10 } 11 12 // 获取全局ENV(环境配置)详情 13 export function getGlobalEnvDetail(pk, params) { 14 return request({ 15 url: '/api_test/global/env/detail/' + pk + '/', 16 method: 'get', 17 params 18 }) 19 } 20 21 // 新增全局ENV(环境配置) 22 export function addGlobalEnv(data) { 23 return request({ 24 url: '/api_test/global/env/add/', 25 method: 'post', 26 data: data 27 }) 28 } 29 30 // 更新全局ENV(环境配置) 31 export function updateGlobalEnv(pk, data) { 32 return request({ 33 url: '/api_test/global/env/update/' + pk + '/', 34 method: 'patch', 35 data 36 }) 37 } 38 39 // 批量 局部更新 40 export function bulkUpdateGlobalEnv(dataArr) { 41 return request({ 42 url: '/api_test/global/env/bulk/', 43 method: 'patch', 44 data: dataArr 45 }) 46 } 47 48 // 删除全局ENV(环境配置) 49 export function deleteGlobalEnv(pk) { 50 return request({ 51 url: '/api_test/global/env/del/' + pk + '/', 52 method: 'delete' 53 }) 54 } 55 56 // 批量 删除 57 export function bulkDeleteGlobalEnv(params) { 58 return request({ 59 url: '/api_test/global/env/bulk/', 60 method: 'delete', 61 params 62 }) 63 } 64 65 // 爬取/获取全局ENV对应环境的数据 66 export function getGlobalEnvData(pk, params) { 67 return request({ 68 url: '/api_test/global/env/data/' + pk + '/', 69 method: 'get', 70 params 71 }) 72 } 73 74 // 获取全局env.config默认数据 75 export function getGlobalEnvConfigDefault(params) { 76 return request({ 77 url: '/api_test/global/env/config/default', 78 method: 'get', 79 params 80 }) 81 } 82 83 // 获取全局env.qw_external_contact_config默认数据 84 export function getGlobalEnvQWExternalContactConfigDefault(params) { 85 return request({ 86 url: '/api_test/global/env/qw_external_contact_config/default', 87 method: 'get', 88 params 89 }) 90 }
- 编写前端页面:environment/index.vue
1 <template> 2 <div class="main" style="padding:10px;"> 3 <!--工具条--> 4 <el-col :span="24" class="toolbar"> 5 <el-form :inline="true" :model="filters" :size="themeSize" @submit.native.prevent> 6 <el-form-item> 7 <el-input v-model.trim="filters.name" placeholder="名称" clearable @keyup.enter.native="fetchData" /> 8 </el-form-item> 9 <el-form-item> 10 <el-button type="primary" @click="fetchData">查询</el-button> 11 </el-form-item> 12 <el-form-item> 13 <el-button type="primary" @click="handleAdd">新增</el-button> 14 </el-form-item> 15 </el-form> 16 </el-col> 17 18 <!--列表--> 19 <el-table 20 v-loading="tableConfig.isLoading" 21 :data="dataList" 22 highlight-current-row 23 :size="themeSize" 24 :height="tableConfig.height" 25 style="width: 100%;" 26 @selection-change="selsChange" 27 > 28 <el-table-column type="selection" min-width="50" /> 29 <el-table-column prop="name" label="名称" sortable show-overflow-tooltip min-width="150" /> 30 <el-table-column prop="description" label="描述" show-overflow-tooltip min-width="200" /> 31 <el-table-column prop="config" label="公司ID" sortable show-overflow-tooltip min-width="150"> 32 <template slot-scope="scope"> 33 {{ scope.row.config['company_id']['value'] }} 34 </template> 35 </el-table-column> 36 <el-table-column prop="config" label="配置详情" min-width="100"> 37 <template slot-scope="scope"> 38 <el-popover 39 placement="right" 40 width="800" 41 trigger="click" 42 > 43 <el-card class="box-card" shadow="never"> 44 <div slot="header" class="clearfix">环境配置({{ scope.row.name }}:{{ scope.row.description }})</div> 45 <el-table :data="dictConfigToArray(scope.row.config, scope.row.qw_external_contact_config)" style="width: 100%" :size="themeSize"> 46 <el-table-column label="名称" prop="key" sortable show-overflow-tooltip /> 47 <el-table-column label="值" prop="value" sortable show-overflow-tooltip /> 48 <el-table-column label="描述" prop="description" sortable show-overflow-tooltip /> 49 </el-table> 50 </el-card> 51 <el-button slot="reference" :size="themeSize" type="text">详情</el-button> 52 </el-popover> 53 </template> 54 </el-table-column> 55 <el-table-column prop="data" label="环境数据" min-width="100"> 56 <template slot-scope="scope"> 57 <el-popover 58 placement="right" 59 width="600" 60 trigger="click" 61 > 62 <el-row> 63 <el-button style="float: right; padding: 3px 0" type="text" @click="clearData(scope.row)">清除数据</el-button> 64 <el-button style="padding-left: 10px" type="primary" :size="themeSize" @click="toggleExpanded">{{ expanded ? '收起' : '展开' }}</el-button> 65 环境数据({{ scope.row.name }}:{{ scope.row.description }}) 66 </el-row> 67 <json-viewer 68 :key="expanded" 69 :value="scope.row.data" 70 :expand-depth="1" 71 :expanded="expanded" 72 copyable 73 boxed 74 sort 75 /> 76 <el-button slot="reference" :size="themeSize" type="text">详情</el-button> 77 </el-popover> 78 </template> 79 </el-table-column> 80 <el-table-column prop="mock" label="Mock数据" min-width="100"> 81 <template slot-scope="scope"> 82 <el-popover 83 placement="right" 84 width="500" 85 trigger="click" 86 > 87 <el-row> 88 <el-button style="float: right;" type="text" :size="themeSize" @click="clearMock(scope.row)">清除数据</el-button> 89 <el-button style="padding-left: 10px" type="primary" :size="themeSize" @click="toggleExpanded">{{ expanded ? '收起' : '展开' }}</el-button> 90 Mock数据,执行时自动更新({{ scope.row.name }}:{{ scope.row.description }}) 91 </el-row> 92 <json-viewer 93 :key="expanded" 94 :value="scope.row.mock" 95 :expand-depth="1" 96 :expanded="expanded" 97 copyable 98 boxed 99 sort 100 /> 101 <el-button slot="reference" :size="themeSize" type="text">详情</el-button> 102 </el-popover> 103 </template> 104 </el-table-column> 105 <el-table-column prop="status" label="状态" sortable min-width="70"> 106 <template slot-scope="scope"> 107 <img v-show="scope.row.status" src="@/assets/icon-yes.svg" alt=""> 108 <img v-show="!scope.row.status" src="@/assets/icon-no.svg" alt=""> 109 </template> 110 </el-table-column> 111 <el-table-column label="操作" min-width="180"> 112 <template slot-scope="scope"> 113 <el-button type="info" icon="el-icon-edit" circle :size="themeSize" title="编辑" @click="handleEdit(scope.$index, scope.row)" /> 114 <el-button type="warning" icon="el-icon-refresh" circle :size="themeSize" title="更新环境数据" @click="handleUpdateEnvData(scope.$index, scope.row)" /> 115 <el-button type="warning" icon="el-icon-s-flag" circle :size="themeSize" :title="scope.row.status===false?'启用':'禁用'" @click="handleChangeStatus(scope.$index, scope.row)" /> 116 <el-button type="danger" icon="el-icon-delete" circle :size="themeSize" :loading="delLoading" title="删除" @click="handleDel(scope.$index, scope.row)" /> 117 </template> 118 </el-table-column> 119 </el-table> 120 121 <!--底部工具条--> 122 <el-row class="toolbar"> 123 <!--分页--> 124 <el-col class="pagination-toolbar" :span="24"> 125 <el-pagination 126 background 127 style="float:right;" 128 :current-page.sync="page" 129 layout="total, sizes, prev, pager, next, jumper" 130 :page-size="page_size" 131 :page-sizes="[20, 50, 100, 1000]" 132 :total="total" 133 @size-change="handleSizeChange" 134 @current-change="handleCurrentChange" 135 /> 136 </el-col> 137 <!--批量处理--> 138 <el-col v-show="sels.length>0" class="bulk-toolbar" :span="24"> 139 <span style="font-weight:bold;font-size:14px;color:#2C8DF4;">批量处理: </span> 140 <el-button type="danger" plain :disabled="sels.length===0" :size="themeSize" @click="bulkRemove">删除</el-button> 141 <span style="font-size: 14px; padding-right: 30px;"> 选中{{ sels.length }}条</span> 142 <el-button type="text" plain :size="themeSize" @click="sels = []">取消</el-button> 143 </el-col> 144 </el-row> 145 146 <!--编辑界面--> 147 <el-drawer 148 title="编辑" 149 :with-header="true" 150 :wrapper-closable="false" 151 :visible.sync="editFormVisible" 152 direction="rtl" 153 size="50%" 154 > 155 <div class="demo-drawer__content"> 156 <el-form ref="editForm" :size="themeSize" :model="editForm" :rules="editFormRules" label-width="160px"> 157 <!-- 基本信息 --> 158 <el-collapse value="1"> 159 <el-collapse-item name="1"> 160 <span slot="title" style="font-weight:bold;font-size:14px;color:#2C8DF4;">基本信息</span> 161 <el-form-item label="名称" prop="name"> 162 <el-input v-model.trim="editForm.name" auto-complete="off" /> 163 </el-form-item> 164 <el-form-item v-for="(k,index) in editForm.config" :key="index" :label="k.description" :prop="index"> 165 <el-input v-model.trim="editForm.config[index].value" auto-complete="off" /> 166 </el-form-item> 167 <el-form-item label="描述" prop="description"> 168 <el-input v-model.trim="editForm.description" type="textarea" :rows="2" /> 169 </el-form-item> 170 </el-collapse-item> 171 </el-collapse> 172 173 <!-- 企微客户联系配置 --> 174 <el-collapse value="1"> 175 <el-collapse-item name="1"> 176 <span slot="title" style="font-weight:bold;font-size:14px;color:#2C8DF4;">企微客户联系配置</span> 177 <el-form-item v-for="(k,index) in editForm.qw_external_contact_config" :key="index" :label="k.description" :prop="index"> 178 <el-input v-model.trim="editForm.qw_external_contact_config[index].value" auto-complete="off" /> 179 </el-form-item> 180 </el-collapse-item> 181 </el-collapse> 182 183 <!-- 设置 --> 184 <el-collapse value="1"> 185 <el-collapse-item name="1"> 186 <span slot="title" style="font-weight:bold;font-size:14px;color:#2C8DF4;">设置</span> 187 <el-form-item label="动态更新mock" prop="mock_dynamic"> 188 <el-switch 189 v-model="editForm.mock_dynamic" 190 active-color="#13ce66" 191 /> 192 </el-form-item> 193 <el-form-item label="默认环境" prop="is_default"> 194 <el-switch 195 v-model="editForm.is_default" 196 active-color="#13ce66" 197 /> 198 </el-form-item> 199 </el-collapse-item> 200 </el-collapse> 201 </el-form> 202 <!-- 取消、提交 --> 203 <div class="demo-drawer__footer"> 204 <el-button :size="themeSize" @click.native="editFormVisible = false; editLoading = false">取消</el-button> 205 <el-button :size="themeSize" type="primary" :loading="editLoading" @click.native="editSubmit">提交</el-button> 206 </div> 207 </div> 208 </el-drawer> 209 210 <!--新增界面--> 211 <el-drawer 212 title="编辑" 213 :with-header="true" 214 :wrapper-closable="false" 215 :visible.sync="addFormVisible" 216 direction="rtl" 217 size="50%" 218 > 219 <div class="demo-drawer__content"> 220 <el-form ref="addForm" :size="themeSize" :model="addForm" label-width="160px" :rules="addFormRules"> 221 <!-- 基本信息 --> 222 <el-collapse value="1"> 223 <el-collapse-item name="1"> 224 <span slot="title" style="font-weight:bold;font-size:14px;color:#2C8DF4;">基本信息</span> 225 <el-form-item label="名称" prop="name"> 226 <el-input v-model.trim="addForm.name" auto-complete="off" /> 227 </el-form-item> 228 <el-form-item v-for="(k,index) in addForm.config" :key="index" :label="k.description" :prop="'config.'+index"> 229 <el-input v-model.trim="addForm.config[index].value" auto-complete="off" /> 230 </el-form-item> 231 <el-form-item label="环境描述" prop="description"> 232 <el-input v-model.trim="addForm.description" type="textarea" :rows="2" /> 233 </el-form-item> 234 </el-collapse-item> 235 </el-collapse> 236 237 <!-- 企微客户联系配置 --> 238 <el-collapse value="1"> 239 <el-collapse-item name="1"> 240 <span slot="title" style="font-weight:bold;font-size:14px;color:#2C8DF4;">企微客户联系配置</span> 241 <el-form-item v-for="(k,index) in addForm.qw_external_contact_config" :key="index" :label="k.description" :prop="'qw_external_contact_config.'+index"> 242 <el-input v-model.trim="addForm.qw_external_contact_config[index].value" auto-complete="off" /> 243 </el-form-item> 244 </el-collapse-item> 245 </el-collapse> 246 247 <!-- 设置 --> 248 <el-collapse value="1"> 249 <el-collapse-item name="1"> 250 <span slot="title" style="font-weight:bold;font-size:14px;color:#2C8DF4;">设置</span> 251 <el-form-item label="动态更新mock" prop="mock_dynamic"> 252 <el-switch 253 v-model="addForm.mock_dynamic" 254 active-color="#13ce66" 255 /> 256 </el-form-item> 257 <el-form-item label="默认环境" prop="is_default"> 258 <el-switch 259 v-model="addForm.is_default" 260 active-color="#13ce66" 261 /> 262 </el-form-item> 263 </el-collapse-item> 264 </el-collapse> 265 266 </el-form> 267 <!-- 取消、提交 --> 268 <div class="demo-drawer__footer"> 269 <el-button :size="themeSize" @click.native="addFormVisible = false; addLoading = false">取消</el-button> 270 <el-button :size="themeSize" type="primary" :loading="addLoading" @click.native="addSubmit">提交</el-button> 271 </div> 272 </div> 273 </el-drawer> 274 </div> 275 </template> 276 277 <script> 278 import JsonViewer from 'vue-json-viewer' 279 import { 280 addGlobalEnv, 281 bulkDeleteGlobalEnv, 282 deleteGlobalEnv, 283 getGlobalEnvConfigDefault, 284 getGlobalEnvQWExternalContactConfigDefault, 285 getGlobalEnvData, 286 getGlobalEnvList, 287 updateGlobalEnv 288 } from '@/api/apiTest/global_env' 289 290 export default { 291 name: 'GlobalEnv', 292 components: { JsonViewer }, 293 data() { 294 return { 295 themeSize: this.$store.state.settings.themeSize, 296 tableConfig: { 297 isLoading: false, 298 height: window.innerHeight - 230 // 下面剩余多少空白部分(即最下面距离底部有多少距离) 299 }, 300 editLoading: false, 301 addLoading: false, 302 syncLoading: false, 303 delLoading: false, 304 batchDelLoading: false, 305 expanded: true, 306 307 filters: { 308 name: '' 309 }, 310 dataList: null, 311 total: 0, 312 page: 1, 313 page_size: 20, 314 page_count: 0, 315 sels: [], // 列表选中列 316 envDefaultConfig: {}, 317 318 editFormVisible: false, // 编辑界面是否显示 319 // 编辑界面数据规则 320 editFormRules: { 321 name: [ 322 { required: true, message: '请输入名称', trigger: 'blur' }, 323 { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' } 324 ], 325 config: [ 326 { required: true, message: '请输入配置信息', trigger: 'blur' } 327 ], 328 description: [ 329 { required: false, message: '请输入描述', trigger: 'blur' }, 330 { max: 1024, message: '不能超过1024个字符', trigger: 'blur' } 331 ] 332 }, 333 // 编辑界面数据 334 editForm: { 335 name: '', 336 config: {}, 337 mock_dynamic: false, 338 is_default: false, 339 description: '' 340 }, 341 342 addFormVisible: false, // 新增界面是否显示 343 // 新增界面数据规则 344 addFormRules: { 345 name: [ 346 { required: true, message: '请输入名称', trigger: 'blur' }, 347 { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' } 348 ], 349 // config: [ 350 // { required: true, message: '请输入配置信息', trigger: 'blur' } 351 // ], 352 description: [ 353 { required: false, message: '请输入描述', trigger: 'blur' }, 354 { max: 1024, message: '不能超过1024个字符', trigger: 'blur' } 355 ] 356 }, 357 // 新增界面数据 358 addForm: { 359 name: '', 360 config: {}, 361 qw_external_contact_config: {}, 362 data: {}, 363 mock: {}, 364 mock_dynamic: false, 365 is_default: false, 366 description: '' 367 } 368 369 } 370 }, 371 created() { 372 }, 373 mounted() { 374 this.fetchEnvConfigDefault() 375 this.fetchEnvQWExternalContactConfigDefault() 376 this.fetchData() 377 }, 378 methods: { 379 // 获取ENV列表 380 fetchData() { 381 this.tableConfig.isLoading = true 382 const params = { 383 page: this.page, 384 page_size: this.page_size, 385 name: this.filters.name 386 } 387 getGlobalEnvList(params).then(response => { 388 const { msg, code } = response 389 this.tableConfig.isLoading = false 390 if (code === 2) { 391 this.total = response.data.count 392 this.dataList = response.data.list 393 } else { 394 this.$message.error({ 395 message: msg, 396 center: true 397 }) 398 } 399 }) 400 }, 401 fetchEnvConfigDefault() { 402 this.syncLoading = true 403 getGlobalEnvConfigDefault({}).then(response => { 404 const { msg, code } = response 405 this.syncLoading = false 406 if (code === 2) { 407 this.addForm.config = response.data 408 for (const k in response.data) { 409 this.addFormRules['config.' + k] = [{ required: true, message: '请输入配置信息' + k, trigger: 'blur' }] 410 } 411 } else { 412 this.$message.error({ 413 message: msg, 414 center: true 415 }) 416 } 417 }) 418 }, 419 fetchEnvQWExternalContactConfigDefault() { 420 this.syncLoading = true 421 getGlobalEnvQWExternalContactConfigDefault({}).then(response => { 422 const { msg, code } = response 423 this.syncLoading = false 424 if (code === 2) { 425 this.addForm.qw_external_contact_config = response.data 426 for (const k in response.data) { 427 this.addFormRules['qw_external_contact_config.' + k] = [{ required: true, message: '请输入配置信息' + k, trigger: 'blur' }] 428 } 429 } else { 430 this.$message.error({ 431 message: msg, 432 center: true 433 }) 434 } 435 }) 436 }, 437 // 选中行 438 selsChange: function(sels) { 439 this.sels = sels 440 }, 441 // 显示编辑界面 442 handleEdit: function(index, row) { 443 this.editFormVisible = true 444 this.editForm = Object.assign({}, row) 445 }, 446 // 显示新增界面 447 handleAdd: function() { 448 this.addFormVisible = true 449 }, 450 // 改变状态: enable/disable 451 handleChangeStatus: function(index, row) { 452 if (row.status) { 453 // 禁用 454 updateGlobalEnv(row.id, { status: false }).then(response => { 455 const { code, msg } = response 456 if (code === 2) { 457 this.$message({ 458 message: '禁用成功', 459 center: true, 460 type: 'success' 461 }) 462 row.status = !row.status 463 } else { 464 this.$message.error({ 465 message: msg, 466 center: true 467 }) 468 } 469 }) 470 } else { 471 // 启用 472 updateGlobalEnv(row.id, { status: true }).then(response => { 473 const { msg, code } = response 474 if (code === 2) { 475 this.$message({ 476 message: '启用成功', 477 center: true, 478 type: 'success' 479 }) 480 row.status = !row.status 481 } else { 482 this.$message.error({ 483 message: msg, 484 center: true 485 }) 486 } 487 }) 488 } 489 }, 490 // 刷新每页数据条数 491 handleSizeChange(val) { 492 console.log(`每页 ${val} 条`) 493 this.page_size = val 494 this.fetchData() 495 }, 496 // 刷新指定页数据 497 handleCurrentChange(val) { 498 console.log(`当前页: ${val}`) 499 this.page = val 500 this.fetchData() 501 }, 502 // 编辑 503 editSubmit: function() { 504 this.$refs.editForm.validate((valid) => { 505 if (valid) { 506 this.$confirm('确认提交吗?', '提示', {}).then(() => { 507 this.editLoading = true 508 // NProgress.start(); 509 updateGlobalEnv(Number(this.editForm.id), this.editForm).then(response => { 510 const { msg, code } = response 511 this.editLoading = false 512 if (code === 2) { 513 this.$message({ 514 message: '修改成功', 515 center: true, 516 type: 'success' 517 }) 518 this.$refs['editForm'].resetFields() 519 this.editFormVisible = false 520 } else if (code === 3) { 521 this.$message.error({ 522 message: msg, 523 center: true 524 }) 525 } else { 526 this.$message.error({ 527 message: msg, 528 center: true 529 }) 530 } 531 }).then(() => { this.fetchData() }) 532 }) 533 } 534 }) 535 }, 536 // 新增 537 addSubmit: function() { 538 this.$refs.addForm.validate((valid) => { 539 if (valid) { 540 this.$confirm('确认提交吗?', '提示', {}).then(() => { 541 this.addLoading = true 542 // NProgress.start(); 543 addGlobalEnv(this.addForm).then(response => { 544 const { msg, code } = response 545 this.addLoading = false 546 if (code === 2) { 547 this.$message({ 548 message: '添加成功', 549 center: true, 550 type: 'success' 551 }) 552 this.$refs['addForm'].resetFields() 553 this.addFormVisible = false 554 } else if (code === 3) { 555 this.$message.error({ 556 message: msg, 557 center: true 558 }) 559 } else { 560 this.$message.error({ 561 message: msg, 562 center: true 563 }) 564 this.$refs['addForm'].resetFields() 565 this.addFormVisible = false 566 } 567 }).then(() => { this.fetchData() }) 568 }) 569 } 570 }) 571 }, 572 // 删除 573 handleDel: function(index, row) { 574 this.$confirm('确认删除该记录吗?', '提示', { 575 type: 'warning' 576 }).then(() => { 577 this.delLoading = true 578 // NProgress.start(); 579 deleteGlobalEnv(row.id).then(response => { 580 const { msg, code } = response 581 this.delLoading = false 582 if (code === 2) { 583 this.$message({ 584 message: '删除成功', 585 center: true, 586 type: 'success' 587 }) 588 } else { 589 this.$message.error({ 590 message: msg, 591 center: true 592 }) 593 } 594 }).then(() => { this.fetchData() }) 595 }) 596 }, 597 // 批量删除 598 bulkRemove: function() { 599 const ids = this.sels.map(item => item.id) 600 this.$confirm('确认删除选中记录吗?', '提示', { 601 type: 'warning' 602 }).then(() => { 603 const params = { 604 id_in: ids.join(',') 605 } 606 bulkDeleteGlobalEnv(params).then(response => { 607 const { code, msg } = response 608 if (code === 2) { 609 this.$message({ 610 message: '删除成功', 611 center: true, 612 type: 'success' 613 }) 614 } else { 615 this.$message.error({ 616 message: msg, 617 center: true 618 }) 619 } 620 }).then(() => { this.fetchData() }) 621 }) 622 }, 623 // 获取环境数据并更新到数据库 624 handleUpdateEnvData: function(index, row) { 625 this.$confirm('确认提交吗?', '提示', {}).then(() => { 626 this.syncLoading = true 627 getGlobalEnvData(Number(row.id), {}).then(response => { 628 const { msg, code } = response 629 this.syncLoading = false 630 if (code === 2) { 631 updateGlobalEnv(Number(row.id), response.data).then(response => { 632 const { msg, code } = response 633 if (code === 2) { 634 this.$message({ 635 message: '更新成功', 636 center: true, 637 type: 'success' 638 }) 639 } else if (code === 3) { 640 this.$message.error({ 641 message: msg, 642 center: true 643 }) 644 } else { 645 this.$message.error({ 646 message: msg, 647 center: true 648 }) 649 } 650 }).then(() => { this.fetchData() }) 651 } else { 652 this.$message.error({ 653 message: msg, 654 center: true 655 }) 656 } 657 }) 658 }) 659 }, 660 // 清除env.data 661 clearData: function(row) { 662 this.$confirm('确认清除env.data数据吗?', '提示', {}).then(() => { 663 updateGlobalEnv(Number(row.id), { data: {}}).then(response => { 664 const { msg, code } = response 665 if (code === 2) { 666 this.$message({ 667 message: '清除成功', 668 center: true, 669 type: 'success' 670 }) 671 this.fetchData() 672 } else if (code === 3) { 673 this.$message.error({ 674 message: msg, 675 center: true 676 }) 677 } 678 }) 679 }) 680 }, 681 clearMock: function(row) { 682 this.$confirm('确认清除env.mock数据吗?', '提示', {}).then(() => { 683 updateGlobalEnv(Number(row.id), { mock: {}}).then(response => { 684 const { msg, code } = response 685 if (code === 2) { 686 this.$message({ 687 message: '清除成功', 688 center: true, 689 type: 'success' 690 }) 691 this.fetchData() 692 } else if (code === 3) { 693 this.$message.error({ 694 message: msg, 695 center: true 696 }) 697 } 698 }) 699 }) 700 }, 701 // env.data/env.mock 字典key-value 转Array[{key:key, value:value}] 702 dictKVToArray: function(src) { 703 const dst = [] 704 for (const k in src) { 705 const v = src[k] 706 dst.push({ key: k, value: JSON.stringify(v) }) 707 } 708 return dst 709 }, 710 // env.config/env.qw_external_contact_config 字典key:{value:'', description:''} 转Array[{key:key, value:value, description:description}] 711 dictConfigToArray: function(src1, src2) { 712 const dst = [] 713 for (const k in src1) { 714 const v = src1[k] 715 dst.push({ key: k, value: JSON.stringify(v.value), description: v.description }) 716 } 717 for (const k in src2) { 718 const v = src2[k] 719 dst.push({ key: k, value: JSON.stringify(v.value), description: v.description }) 720 } 721 return dst 722 }, 723 toggleExpanded() { 724 this.expanded = !this.expanded 725 } 726 } 727 } 728 </script> 729 730 <style lang="scss" scoped> 731 ::v-deep .el-drawer__body { 732 overflow: auto; 733 } 734 ::v-deep .demo-drawer__content { 735 margin-bottom: 2px; 736 padding: 10px 20px 20px; 737 overflow: auto; 738 } 739 ::v-deep .demo-drawer__footer{ 740 width: 100%; 741 position: absolute; 742 bottom: 0; 743 left: 0; 744 border-top: 1px solid #e8e8e8; 745 padding: 10px 16px; 746 text-align: center; 747 background-color: white; 748 } 749 </style>
3. 页面组件化
示例:工作台页面 - 快速测试、业务巡检、冒烟测试、环境验证
- components/TabsMenu.vue
1 <template> 2 <div class="main"> 3 <!-- 标签页 --> 4 <el-tabs v-model="activeName" :tab-position="tabPosition" @tab-click="clickTab"> 5 <el-tab-pane 6 v-for="(item,index) in tabPaneList" 7 :key="index" 8 :label="item.label" 9 :name="item.name" 10 lazy 11 > 12 <component :is="component" v-for="(component,idx) in item.components" :key="idx" /> 13 </el-tab-pane> 14 </el-tabs> 15 </div> 16 </template> 17 18 <script> 19 export default { 20 name: '', 21 props: { 22 tabPaneList: { 23 type: Array, 24 default() { 25 return [] 26 } 27 }, 28 tabPosition: { 29 type: String, 30 default() { 31 return 'top' 32 } 33 } 34 }, 35 data() { 36 return { 37 activeName: '' // 默认选中 38 } 39 }, 40 created() { 41 this.getMenuList() 42 }, 43 mounted() { 44 }, 45 methods: { 46 // 获取标签页列表 47 getMenuList() { 48 if (this.tabPaneList.length > 0) { 49 this.activeName = this.tabPaneList[0].name 50 } 51 }, 52 // 控制每次点击标签,都重新请求子组件的接口 53 clickTab(tab, event) { 54 // console.log(tab, event) 55 } 56 } 57 } 58 </script> 59 60 <style lang="scss" scoped> 61 ::v-deep .el-tabs__item { 62 box-sizing: border-box; 63 height: 35px; 64 color: #2c4068; 65 margin: 0; 66 text-align: center; 67 overflow: hidden!important; 68 text-overflow: ellipsis; 69 white-space: nowrap; 70 word-break: break-all; 71 font-weight: 400; 72 min-width: 100px; 73 font-size: 14px; 74 line-height: 22px; 75 padding: 9px 0; 76 position: relative; 77 display: inline-block; 78 list-style: none; 79 } 80 ::v-deep .el-tabs__item.is-active { 81 color: #047AE2; 82 font-weight: 700; 83 } 84 ::v-deep .el-tabs__active-bar { 85 height: 4px; 86 position: absolute; 87 bottom: 0; 88 left: 0; 89 z-index: 1; 90 list-style: none; 91 } 92 ::v-deep .el-tabs--left .el-tabs__item.is-left { 93 text-align: center; 94 } 95 </style>
- workbench/index.vue
1 <template> 2 <tabs-menu :tab-pane-list="tabPaneList" /> 3 </template> 4 5 <script> 6 import TabsMenu from '@/views/apiTest/components/TabsMenu' 7 // import jobBoard from '@/views/apiTest/workbench/jobBoard' 8 import FastTest from '@/views/apiTest/workbench/FastTest' 9 import EnvInspection from '@/views/apiTest/workbench/EnvInspection' 10 import EnvSmoke from '@/views/apiTest/workbench/EnvSmoke' 11 import EnvValidate from '@/views/apiTest/workbench/EnvValidate' 12 // import task from '@/views/apiTest/taskMgr/TaskSchedule' 13 14 export default { 15 name: 'Workbench', 16 components: { TabsMenu }, 17 data() { 18 return { 19 tabPaneList: [ 20 // { 21 // name: 'jobBoard', 22 // label: '工作看板', 23 // components: [jobBoard] 24 // }, 25 { 26 name: 'FastTest', 27 label: '快速测试', 28 components: [FastTest] 29 }, 30 { 31 name: 'EnvInspection', 32 label: '业务巡检', 33 components: [EnvInspection] 34 }, 35 { 36 name: 'EnvSmoke', 37 label: '冒烟测试', 38 components: [EnvSmoke] 39 }, 40 { 41 name: 'EnvValidate', 42 label: '环境验证', 43 components: [EnvValidate] 44 } 45 ] 46 } 47 } 48 } 49 </script> 50 51 <style scoped> 52 53 </style>
-------- THE END --------