增加测试平台功能,系统设置,支持多个角色,分配菜单

This commit is contained in:
qiaoxinjiu
2026-04-22 10:00:17 +08:00
parent 6d5d69cbde
commit 4395d9fb22
42 changed files with 4239 additions and 81 deletions

View File

@@ -0,0 +1,116 @@
<template>
<div class="page-wrap">
<page-section :title="form.id ? '编辑用例' : '新建用例'">
<el-form ref="form" :model="form" :rules="rules" label-width="120px" size="small">
<el-form-item label="项目ID">
<el-input v-model="projectId" style="width: 200px;"></el-input>
</el-form-item>
<el-form-item label="标题" prop="title">
<el-input v-model="form.title"></el-input>
</el-form-item>
<el-form-item label="前置条件">
<el-input v-model="form.preconditions" type="textarea" :rows="3"></el-input>
</el-form-item>
<el-form-item label="步骤(JSON)" prop="steps">
<el-input v-model="stepsText" type="textarea" :rows="10"></el-input>
</el-form-item>
<el-form-item label="优先级">
<el-select v-model="form.priority">
<el-option label="P0" :value="0"></el-option>
<el-option label="P1" :value="1"></el-option>
<el-option label="P2" :value="2"></el-option>
<el-option label="P3" :value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="form.case_type">
<el-option label="功能" :value="1"></el-option>
<el-option label="性能" :value="2"></el-option>
<el-option label="安全" :value="3"></el-option>
<el-option label="接口" :value="4"></el-option>
</el-select>
</el-form-item>
<el-form-item label="标签">
<el-input v-model="tagsText" placeholder="多个标签用逗号分隔"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="submitForm">保存</el-button>
<el-button @click="$router.push('/test-platform/cases')">返回</el-button>
</el-form-item>
</el-form>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import { createCase, getCaseDetail, updateCase } from '@/api/caseApi'
export default {
name: 'CaseEditor',
components: { PageSection },
data() {
return {
saving: false,
projectId: this.$route.query.projectId || 1,
form: {
id: '',
title: '',
preconditions: '',
steps: [],
priority: 2,
case_type: 1,
tags: []
},
stepsText: '[]',
tagsText: '',
rules: {
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
steps: [{ required: true, message: '请输入步骤', trigger: 'change' }]
}
}
},
methods: {
fetchDetail() {
const caseId = this.$route.query.caseId
if (!caseId) {
return
}
getCaseDetail(this.projectId, caseId).then(res => {
const data = (res && res.data) || res || {}
this.form = Object.assign({}, this.form, data)
this.stepsText = JSON.stringify(data.steps || [], null, 2)
this.tagsText = (data.tags || []).join(',')
})
},
submitForm() {
try {
this.form.steps = JSON.parse(this.stepsText || '[]')
} catch (e) {
this.$message({ type: 'error', message: '步骤 JSON 格式错误' })
return
}
this.form.tags = this.tagsText ? this.tagsText.split(',').map(item => item.trim()).filter(Boolean) : []
this.saving = true
const request = this.form.id || this.$route.query.caseId
? updateCase(this.projectId, this.form.id || this.$route.query.caseId, this.form)
: createCase(this.projectId, this.form)
request.then(() => {
this.$message({ type: 'success', message: '保存成功' })
this.$router.push({ path: '/test-platform/cases', query: { projectId: this.projectId } })
}).finally(() => {
this.saving = false
})
}
},
created() {
this.fetchDetail()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<div class="page-wrap">
<page-section title="用例管理">
<template slot="extra">
<el-button type="primary" size="small" @click="goEditor()">新建用例</el-button>
</template>
<el-form :inline="true" :model="queryForm" size="small" @submit.native.prevent>
<el-form-item label="项目ID">
<el-input v-model="projectId" style="width: 120px;"></el-input>
</el-form-item>
<el-form-item label="关键词">
<el-input v-model="queryForm.keyword" clearable @keyup.enter.native="fetchList"></el-input>
</el-form-item>
<el-form-item label="优先级">
<el-select v-model="queryForm.priority" clearable>
<el-option label="P0" :value="0"></el-option>
<el-option label="P1" :value="1"></el-option>
<el-option label="P2" :value="2"></el-option>
<el-option label="P3" :value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchList">查询</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="tableData" border style="margin-top: 16px;">
<el-table-column prop="case_key" label="用例编号" min-width="120"></el-table-column>
<el-table-column prop="title" label="标题" min-width="220"></el-table-column>
<el-table-column prop="priority" label="优先级" width="100"></el-table-column>
<el-table-column prop="case_type" label="类型" width="100"></el-table-column>
<el-table-column prop="status" label="状态" width="100"></el-table-column>
<el-table-column label="操作" width="240">
<template slot-scope="scope">
<el-button type="text" @click="goEditor(scope.row)">编辑</el-button>
<el-button type="text" @click="goReview(scope.row)">评审</el-button>
<el-button type="text" style="color: #F56C6C;" @click="remove(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 16px; text-align: right;">
<el-pagination
:current-page="pageNo"
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange">
</el-pagination>
</div>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import { deleteCase, getCaseList } from '@/api/caseApi'
export default {
name: 'CaseList',
components: { PageSection },
data() {
return {
loading: false,
projectId: this.$route.query.projectId || 1,
queryForm: {
keyword: '',
priority: ''
},
tableData: []
}
},
methods: {
fetchList() {
this.loading = true
getCaseList(this.projectId, this.queryForm).then(res => {
const data = (res && res.data) || res || {}
this.tableData = data.items || data.list || []
}).catch(() => {
this.tableData = []
}).finally(() => {
this.loading = false
})
},
goEditor(row) {
this.$router.push({ path: '/test-platform/cases/editor', query: { projectId: this.projectId, caseId: row && row.id } })
},
goReview(row) {
this.$router.push({ path: '/test-platform/cases/review', query: { projectId: this.projectId, caseId: row.id } })
},
remove(row) {
this.$confirm('确认删除该用例吗?', '提示', { type: 'warning' }).then(() => {
deleteCase(this.projectId, row.id).then(() => {
this.$message({ type: 'success', message: '删除成功' })
this.fetchList()
})
}).catch(() => {})
}
},
created() {
this.fetchList()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,64 @@
<template>
<div class="page-wrap">
<page-section title="用例评审">
<el-form :model="form" label-width="120px" size="small">
<el-form-item label="项目ID">
<el-input v-model="projectId" style="width: 200px;"></el-input>
</el-form-item>
<el-form-item label="用例ID">
<el-input v-model="caseId" disabled style="width: 200px;"></el-input>
</el-form-item>
<el-form-item label="评审人ID">
<el-input v-model="reviewersText" placeholder="多个 ID 用逗号分隔"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.comments" type="textarea" :rows="4"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="submitting" @click="submitReview">提交评审</el-button>
</el-form-item>
</el-form>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import { submitCaseReview } from '@/api/caseApi'
export default {
name: 'CaseReview',
components: { PageSection },
data() {
return {
projectId: this.$route.query.projectId || 1,
caseId: this.$route.query.caseId || '',
reviewersText: '',
submitting: false,
form: {
comments: ''
}
}
},
methods: {
submitReview() {
const reviewer_ids = this.reviewersText.split(',').map(item => item.trim()).filter(Boolean)
this.submitting = true
submitCaseReview(this.projectId, this.caseId, {
reviewer_ids,
comments: this.form.comments
}).then(() => {
this.$message({ type: 'success', message: '评审提交成功' })
}).finally(() => {
this.submitting = false
})
}
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,82 @@
<template>
<div class="page-wrap">
<page-section title="造数器编辑">
<el-form :model="form" label-width="120px" size="small">
<el-form-item label="项目ID">
<el-input v-model="projectId" style="width: 200px;"></el-input>
</el-form-item>
<el-form-item label="名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="form.builder_type">
<el-option label="流程编排" :value="1"></el-option>
<el-option label="SQL" :value="2"></el-option>
<el-option label="脚本" :value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" type="textarea" :rows="3"></el-input>
</el-form-item>
<el-form-item label="定义(JSON)">
<el-input v-model="definitionText" type="textarea" :rows="14"></el-input>
</el-form-item>
<el-form-item label="输入Schema(JSON)">
<el-input v-model="inputSchemaText" type="textarea" :rows="8"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="submitForm">保存</el-button>
</el-form-item>
</el-form>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import { createBuilder } from '@/api/dataFactoryApi'
export default {
name: 'BuilderEditor',
components: { PageSection },
data() {
return {
saving: false,
projectId: this.$route.query.projectId || 1,
definitionText: '{\n "steps": [],\n "output": {}\n}',
inputSchemaText: '{\n "type": "object",\n "properties": {}\n}',
form: {
name: '',
builder_type: 1,
description: ''
}
}
},
methods: {
submitForm() {
let definition = {}
let input_schema = {}
try {
definition = JSON.parse(this.definitionText || '{}')
input_schema = JSON.parse(this.inputSchemaText || '{}')
} catch (e) {
this.$message({ type: 'error', message: 'JSON 格式错误' })
return
}
this.saving = true
createBuilder(this.projectId, Object.assign({}, this.form, { definition, input_schema })).then(() => {
this.$message({ type: 'success', message: '造数器保存成功' })
this.$router.push({ path: '/test-platform/data-factory/builders', query: { projectId: this.projectId } })
}).finally(() => {
this.saving = false
})
}
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<div class="page-wrap">
<page-section title="造数工厂">
<template slot="extra">
<el-button type="primary" size="small" @click="goEditor()">新建造数器</el-button>
<el-button size="small" @click="goTasks">任务历史</el-button>
<el-button size="small" @click="goMock">Mock服务</el-button>
</template>
<el-form :inline="true" size="small">
<el-form-item label="项目ID">
<el-input v-model="projectId" style="width: 120px;"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchList">查询</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="tableData" border style="margin-top: 16px;">
<el-table-column prop="name" label="名称" min-width="160"></el-table-column>
<el-table-column prop="builder_type" label="类型" width="120"></el-table-column>
<el-table-column prop="description" label="描述" min-width="220"></el-table-column>
<el-table-column label="操作" width="240">
<template slot-scope="scope">
<el-button type="text" @click="goEditor(scope.row)">编辑</el-button>
<el-button type="text" @click="execute(scope.row)">执行</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 16px; text-align: right;">
<el-pagination
:current-page="pageNo"
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange">
</el-pagination>
</div>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import { executeBuilder, getBuilderList } from '@/api/dataFactoryApi'
export default {
name: 'BuilderList',
components: { PageSection },
data() {
return {
loading: false,
projectId: this.$route.query.projectId || 1,
tableData: []
}
},
methods: {
fetchList() {
this.loading = true
getBuilderList(this.projectId, {
pageNo: this.pageNo,
pageSize: this.pageSize
}).then(res => {
const data = (res && res.data) || res || []
this.tableData = data.items || data.list || data.data || data || []
this.total = data.total || data.totalCount || this.tableData.length
}).catch(() => {
this.tableData = []
this.total = 0
}).finally(() => {
this.loading = false
})
},
goEditor(row) {
this.$router.push({ path: '/test-platform/data-factory/editor', query: { projectId: this.projectId, builderId: row && row.id } })
},
goTasks() {
this.$router.push({ path: '/test-platform/data-factory/tasks', query: { projectId: this.projectId } })
},
goMock() {
this.$router.push({ path: '/test-platform/data-factory/mock', query: { projectId: this.projectId } })
},
execute(row) {
executeBuilder(this.projectId, row.id, { params: { count: 1 }, async: true }).then(() => {
this.$message({ type: 'success', message: '造数任务已提交' })
})
}
},
created() {
this.fetchList()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>
style>

View File

@@ -0,0 +1,72 @@
<template>
<div class="page-wrap">
<page-section title="Mock 服务">
<el-form :model="form" label-width="120px" size="small">
<el-form-item label="项目ID">
<el-input v-model="projectId" style="width: 200px;"></el-input>
</el-form-item>
<el-form-item label="Path">
<el-input v-model="form.path"></el-input>
</el-form-item>
<el-form-item label="Method">
<el-select v-model="form.method">
<el-option label="GET" value="GET"></el-option>
<el-option label="POST" value="POST"></el-option>
<el-option label="PUT" value="PUT"></el-option>
<el-option label="DELETE" value="DELETE"></el-option>
</el-select>
</el-form-item>
<el-form-item label="状态码">
<el-input v-model="form.status_code"></el-input>
</el-form-item>
<el-form-item label="延迟(ms)">
<el-input v-model="form.delay_ms"></el-input>
</el-form-item>
<el-form-item label="响应体(JSON)">
<el-input v-model="responseBodyText" type="textarea" :rows="10"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="submitForm">创建 Mock</el-button>
</el-form-item>
</el-form>
<el-alert v-if="mockUrl" :title="'Mock 地址:' + mockUrl" type="success" :closable="false"></el-alert>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
export default {
name: 'MockService',
components: { PageSection },
data() {
return {
saving: false,
mockUrl: '',
projectId: this.$route.query.projectId || 1,
responseBodyText: '{\n "code": 0,\n "message": "success"\n}',
form: {
path: '',
method: 'POST',
status_code: 200,
delay_ms: 0
}
}
},
methods: {
submitForm() {
this.$message({
type: 'warning',
message: '当前后端未提供 Mock 服务接口,页面暂为占位状态'
})
}
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div class="page-wrap">
<page-section title="造数任务历史">
<el-form :inline="true" size="small">
<el-form-item label="项目ID">
<el-input v-model="projectId" style="width: 120px;"></el-input>
</el-form-item>
<el-form-item label="任务ID">
<el-input v-model="taskId" style="width: 160px;"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchStatus">查询状态</el-button>
</el-form-item>
</el-form>
<json-viewer :value="taskResult"></json-viewer>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import JsonViewer from '@/components/TestPlatform/common/JsonViewer'
import { getDataTaskStatus } from '@/api/dataFactoryApi'
export default {
name: 'TaskHistory',
components: { PageSection, JsonViewer },
data() {
return {
projectId: this.$route.query.projectId || 1,
taskId: this.$route.query.taskId || '',
taskResult: {}
}
},
methods: {
fetchStatus() {
if (!this.taskId) {
this.$message({ type: 'warning', message: '请输入任务ID' })
return
}
getDataTaskStatus(this.projectId, this.taskId).then(res => {
this.taskResult = (res && res.data) || res || {}
}).catch(() => {
this.taskResult = {}
})
}
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div class="page-wrap">
<page-section title="计划构建">
<el-form :model="form" label-width="120px" size="small">
<el-form-item label="项目ID">
<el-input v-model="projectId" style="width: 200px;"></el-input>
</el-form-item>
<el-form-item label="计划名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="版本">
<el-input v-model="form.version"></el-input>
</el-form-item>
<el-form-item label="负责人ID">
<el-input v-model="form.owner_id"></el-input>
</el-form-item>
<el-form-item label="环境ID">
<el-input v-model="form.environment_id"></el-input>
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" type="textarea" :rows="4"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="submitForm">保存计划</el-button>
</el-form-item>
</el-form>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import { createPlan } from '@/api/planApi'
export default {
name: 'PlanBuilder',
components: { PageSection },
data() {
return {
saving: false,
projectId: this.$route.query.projectId || 1,
form: {
name: '',
version: '',
owner_id: '',
environment_id: '',
description: ''
}
}
},
methods: {
submitForm() {
this.saving = true
createPlan(this.projectId, this.form).then(() => {
this.$message({ type: 'success', message: '计划创建成功' })
this.$router.push({ path: '/test-platform/plans', query: { projectId: this.projectId } })
}).finally(() => {
this.saving = false
})
}
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,114 @@
<template>
<div class="page-wrap">
<page-section title="计划执行">
<el-form :inline="true" size="small" @submit.native.prevent>
<el-form-item label="项目ID">
<el-input v-model="projectId" style="width: 120px;"></el-input>
</el-form-item>
<el-form-item label="计划ID">
<el-input v-model="planId" style="width: 120px;"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchDetail">刷新</el-button>
</el-form-item>
</el-form>
<key-value-descriptions :items="summaryItems"></key-value-descriptions>
<el-divider></el-divider>
<el-form :model="executeForm" label-width="120px" size="small">
<el-form-item label="计划用例ID">
<el-input v-model="executeForm.planCaseId"></el-input>
</el-form-item>
<el-form-item label="执行状态">
<el-select v-model="executeForm.status">
<el-option label="通过" :value="1"></el-option>
<el-option label="失败" :value="2"></el-option>
<el-option label="阻塞" :value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="实际结果">
<el-input v-model="executeForm.actual_result" type="textarea" :rows="4"></el-input>
</el-form-item>
<el-form-item label="缺陷链接">
<el-input v-model="defectLinksText" placeholder="多个缺陷号用逗号分隔"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="submitting" @click="submitExecute">提交执行</el-button>
</el-form-item>
</el-form>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import KeyValueDescriptions from '@/components/TestPlatform/common/KeyValueDescriptions'
import { executePlanCase, getPlanDetail } from '@/api/planApi'
export default {
name: 'PlanExecute',
components: { PageSection, KeyValueDescriptions },
data() {
return {
projectId: this.$route.query.projectId || 1,
planId: this.$route.query.planId || '',
detail: {},
submitting: false,
defectLinksText: '',
executeForm: {
planCaseId: '',
status: 1,
actual_result: ''
}
}
},
computed: {
summaryItems() {
return [
{ label: '计划ID', value: this.detail.id },
{ label: '计划名称', value: this.detail.name },
{ label: '总用例数', value: this.detail.total_cases },
{ label: '已完成', value: this.detail.completed },
{ label: '通过率', value: this.detail.pass_rate }
]
}
},
methods: {
fetchDetail() {
if (!this.planId) {
return
}
getPlanDetail(this.projectId, this.planId).then(res => {
this.detail = (res && res.data) || res || {}
}).catch(() => {
this.detail = {}
})
},
submitExecute() {
if (!this.executeForm.planCaseId) {
this.$message({ type: 'warning', message: '请输入计划用例ID' })
return
}
this.submitting = true
executePlanCase(this.projectId, this.planId, this.executeForm.planCaseId, {
status: this.executeForm.status,
actual_result: this.executeForm.actual_result,
defect_links: this.defectLinksText ? this.defectLinksText.split(',').map(item => item.trim()).filter(Boolean) : [],
attachments: []
}).then(() => {
this.$message({ type: 'success', message: '执行结果已提交' })
}).finally(() => {
this.submitting = false
})
}
},
created() {
this.fetchDetail()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,106 @@
<template>
<div class="page-wrap">
<page-section title="测试计划">
<template slot="extra">
<el-button type="primary" size="small" @click="goBuilder()">新建计划</el-button>
</template>
<el-form :inline="true" :model="queryForm" size="small" @submit.native.prevent>
<el-form-item label="项目ID">
<el-input v-model="projectId" style="width: 120px;"></el-input>
</el-form-item>
<el-form-item label="关键词">
<el-input v-model="queryForm.keyword" clearable></el-input>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryForm.status" clearable>
<el-option label="草稿" :value="0"></el-option>
<el-option label="进行中" :value="1"></el-option>
<el-option label="已完成" :value="2"></el-option>
<el-option label="已归档" :value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchList">查询</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="tableData" border style="margin-top: 16px;">
<el-table-column prop="name" label="计划名称" min-width="180"></el-table-column>
<el-table-column prop="version" label="版本" width="120"></el-table-column>
<el-table-column prop="owner_id" label="负责人" width="120"></el-table-column>
<el-table-column prop="status" label="状态" width="100"></el-table-column>
<el-table-column label="操作" width="260">
<template slot-scope="scope">
<el-button type="text" @click="goExecute(scope.row)">执行</el-button>
<el-button type="text" @click="goProgress(scope.row)">进度</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 16px; text-align: right;">
<el-pagination
:current-page="pageNo"
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange">
</el-pagination>
</div>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import { getPlanList } from '@/api/planApi'
export default {
name: 'PlanList',
components: { PageSection },
data() {
return {
loading: false,
projectId: this.$route.query.projectId || 1,
queryForm: {
keyword: '',
status: ''
},
pageNo: 1,
pageSize: 10,
total: 0,
tableData: []
}
},
methods: {
fetchList() {
this.loading = true
getPlanList(this.projectId, this.queryForm).then(res => {
const data = (res && res.data) || res || {}
this.tableData = data.items || data.list || []
}).catch(() => {
this.tableData = []
}).finally(() => {
this.loading = false
})
},
goBuilder() {
this.$router.push({ path: '/test-platform/plans/builder', query: { projectId: this.projectId } })
},
goExecute(row) {
this.$router.push({ path: '/test-platform/plans/execute', query: { projectId: this.projectId, planId: row.id } })
},
goProgress(row) {
this.$router.push({ path: '/test-platform/plans/progress', query: { projectId: this.projectId, planId: row.id } })
}
},
created() {
this.fetchList()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,73 @@
<template>
<div class="page-wrap">
<page-section title="计划进度">
<el-form :inline="true" size="small">
<el-form-item label="项目ID">
<el-input v-model="projectId" style="width: 120px;"></el-input>
</el-form-item>
<el-form-item label="计划ID">
<el-input v-model="planId" style="width: 120px;"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchProgress">刷新</el-button>
</el-form-item>
</el-form>
<el-row :gutter="16">
<el-col :span="8">
<page-section title="轮次汇总">
<json-viewer :value="progress.round_summary || []"></json-viewer>
</page-section>
</el-col>
<el-col :span="8">
<page-section title="人员负载">
<json-viewer :value="progress.assignee_load || []"></json-viewer>
</page-section>
</el-col>
<el-col :span="8">
<page-section title="每日趋势">
<json-viewer :value="progress.daily_trend || []"></json-viewer>
</page-section>
</el-col>
</el-row>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import JsonViewer from '@/components/TestPlatform/common/JsonViewer'
import { getPlanProgress } from '@/api/planApi'
export default {
name: 'PlanProgress',
components: { PageSection, JsonViewer },
data() {
return {
projectId: this.$route.query.projectId || 1,
planId: this.$route.query.planId || '',
progress: {}
}
},
methods: {
fetchProgress() {
if (!this.planId) {
return
}
getPlanProgress(this.projectId, this.planId).then(res => {
this.progress = (res && res.data) || res || {}
}).catch(() => {
this.progress = {}
})
}
},
created() {
this.fetchProgress()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,223 @@
<template>
<div class="page-wrap">
<page-section title="产品管理">
<template slot="extra">
<el-button type="primary" size="small" @click="openCreate">新建产品</el-button>
</template>
<el-form :inline="true" :model="queryForm" size="small" @submit.native.prevent>
<el-form-item label="产品名称">
<el-input v-model="queryForm.name" placeholder="请输入产品名称" clearable @keyup.enter.native="handleSearch"></el-input>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryForm.status" clearable placeholder="全部状态">
<el-option label="启用" :value="1"></el-option>
<el-option label="禁用" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="tableData" border style="width: 100%; margin-top: 16px;">
<el-table-column prop="code" label="产品编码" min-width="120"></el-table-column>
<el-table-column prop="name" label="产品名称" min-width="160"></el-table-column>
<el-table-column prop="description" label="描述" min-width="220" show-overflow-tooltip></el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template slot-scope="scope">{{ scope.row.status === 0 ? '禁用' : '启用' }}</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<template slot-scope="scope">
<el-button type="text" @click="openEdit(scope.row)">编辑</el-button>
<el-button type="text" style="color: #F56C6C;" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 16px; text-align: right;">
<el-pagination
:current-page="pageNo"
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange">
</el-pagination>
</div>
</page-section>
<el-dialog :title="dialogMode === 'create' ? '新建产品' : '编辑产品'" :visible.sync="dialogVisible" width="520px" @close="resetDialogForm">
<el-form ref="productForm" :model="productForm" :rules="productRules" label-width="94px" size="small">
<el-form-item label="产品名称" prop="name">
<el-input v-model.trim="productForm.name" maxlength="64" placeholder="请输入产品名称"></el-input>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="productForm.status" placeholder="请选择状态" style="width: 100%;">
<el-option label="启用" :value="1"></el-option>
<el-option label="禁用" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model.trim="productForm.description" type="textarea" :rows="4" maxlength="255" show-word-limit placeholder="请输入描述"></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button size="small" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" size="small" :loading="submitting" @click="submitForm">确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import { createProduct, deleteProduct, getProductList, updateProduct } from '@/api/productApi'
const getDefaultForm = () => ({
id: undefined,
name: '',
status: 1,
description: ''
})
export default {
name: 'ProductList',
components: { PageSection },
data() {
return {
loading: false,
submitting: false,
dialogVisible: false,
dialogMode: 'create',
queryForm: {
name: '',
status: ''
},
productForm: getDefaultForm(),
productRules: {
name: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
},
pageNo: 1,
pageSize: 10,
total: 0,
tableData: []
}
},
methods: {
fetchList() {
this.loading = true
getProductList({
pageNo: this.pageNo,
pageSize: this.pageSize,
name: this.queryForm.name,
status: this.queryForm.status
}).then(res => {
const data = res && res.data ? res.data : res || {}
this.tableData = data.items || data.list || data.data || []
this.total = data.total || data.totalCount || this.tableData.length
}).catch(() => {
this.tableData = []
this.total = 0
}).finally(() => {
this.loading = false
})
},
handleSearch() {
this.pageNo = 1
this.fetchList()
},
resetSearch() {
this.queryForm = {
name: '',
status: ''
}
this.pageNo = 1
this.fetchList()
},
handleSizeChange(val) {
this.pageSize = val
this.pageNo = 1
this.fetchList()
},
handleCurrentChange(val) {
this.pageNo = val
this.fetchList()
},
openCreate() {
this.dialogMode = 'create'
this.dialogVisible = true
this.$nextTick(() => {
this.productForm = getDefaultForm()
if (this.$refs.productForm) {
this.$refs.productForm.clearValidate()
}
})
},
openEdit(row) {
this.dialogMode = 'edit'
this.dialogVisible = true
this.$nextTick(() => {
this.productForm = Object.assign(getDefaultForm(), row, { id: row.id })
if (this.$refs.productForm) {
this.$refs.productForm.clearValidate()
}
})
},
resetDialogForm() {
this.productForm = getDefaultForm()
this.submitting = false
this.$nextTick(() => {
if (this.$refs.productForm) {
this.$refs.productForm.resetFields()
}
})
},
submitForm() {
this.$refs.productForm.validate(valid => {
if (!valid) {
return
}
this.submitting = true
const request = this.dialogMode === 'create'
? createProduct(this.productForm)
: updateProduct(this.productForm)
request.then(res => {
if (res && res.code === 20000) {
this.$message.success(res.message || (this.dialogMode === 'create' ? '产品创建成功' : '产品更新成功'))
this.dialogVisible = false
this.pageNo = 1
this.fetchList()
return
}
this.$message.error((res && res.message) || (this.dialogMode === 'create' ? '产品创建失败' : '产品更新失败'))
}).finally(() => {
this.submitting = false
})
})
},
handleDelete(row) {
this.$confirm('确认删除该产品吗?', '提示', {
type: 'warning'
}).then(() => {
deleteProduct({ id: row.id }).then(() => {
this.$message.success('产品删除成功')
if (this.tableData.length === 1 && this.pageNo > 1) {
this.pageNo = this.pageNo - 1
}
this.fetchList()
})
}).catch(() => {})
}
},
created() {
this.fetchList()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div class="page-wrap">
<page-section title="项目详情">
<key-value-descriptions :items="descriptionItems"></key-value-descriptions>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import KeyValueDescriptions from '@/components/TestPlatform/common/KeyValueDescriptions'
import { getProjectDetail } from '@/api/projectApi'
export default {
name: 'ProjectDetail',
components: { PageSection, KeyValueDescriptions },
data() {
return {
detail: {}
}
},
computed: {
descriptionItems() {
return [
{ label: '项目ID', value: this.detail.id },
{ label: '项目标识', value: this.detail.key },
{ label: '项目名称', value: this.detail.name },
{ label: '部门', value: this.detail.department },
{ label: '状态', value: this.detail.status === 0 ? '禁用' : '启用' },
{ label: '描述', value: this.detail.description },
{ label: '扩展配置', value: this.detail.config }
]
}
},
methods: {
fetchDetail() {
const projectId = this.$route.query.projectId || 1
getProjectDetail(projectId).then(res => {
this.detail = (res && res.data) || res || {}
}).catch(() => {
this.detail = {}
})
}
},
created() {
this.fetchDetail()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,247 @@
<template>
<div class="page-wrap">
<page-section title="项目管理">
<template slot="extra">
<el-button type="primary" size="small" @click="openCreate">新建项目</el-button>
</template>
<el-form :inline="true" :model="queryForm" size="small" @submit.native.prevent>
<el-form-item label="关键词">
<el-input v-model="queryForm.keyword" placeholder="项目名称" clearable @keyup.enter.native="fetchList"></el-input>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryForm.status" clearable placeholder="全部状态">
<el-option label="启用" :value="1"></el-option>
<el-option label="禁用" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchList">查询</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="tableData" border style="width: 100%; margin-top: 16px;">
<el-table-column prop="product_name" label="产品名称" min-width="160"></el-table-column>
<el-table-column prop="key" label="项目编码" min-width="120"></el-table-column>
<el-table-column prop="name" label="项目名称" min-width="160"></el-table-column>
<el-table-column prop="description" label="描述" min-width="220" show-overflow-tooltip></el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template slot-scope="scope">{{ scope.row.status === 0 ? '禁用' : '启用' }}</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<template slot-scope="scope">
<el-button type="text" @click="openEdit(scope.row)">编辑</el-button>
<el-button type="text" style="color: #F56C6C;" @click="handleDelete(scope.row)">删除</el-button>
<el-button type="text" @click="goSettings(scope.row)">设置</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 16px; text-align: right;">
<el-pagination
:current-page="pageNo"
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange">
</el-pagination>
</div>
</page-section>
<el-dialog :title="dialogMode === 'create' ? '新建项目' : '编辑项目'" :visible.sync="createDialogVisible" width="520px" @close="resetCreateForm">
<el-form ref="createForm" :model="createForm" :rules="createRules" label-width="94px" size="small">
<el-form-item label="项目名称" prop="name">
<el-input v-model.trim="createForm.name" maxlength="64" placeholder="请输入项目名称"></el-input>
</el-form-item>
<el-form-item label="所属产品" prop="productId">
<el-select v-model="createForm.productId" placeholder="请选择所属产品" filterable clearable style="width: 100%;">
<el-option
v-for="item in productOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="createForm.status" placeholder="请选择状态" style="width: 100%;">
<el-option label="启用" :value="1"></el-option>
<el-option label="禁用" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model.trim="createForm.description" type="textarea" :rows="4" maxlength="255" show-word-limit placeholder="请输入描述"></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button size="small" @click="createDialogVisible = false">取消</el-button>
<el-button type="primary" size="small" :loading="createSubmitting" @click="submitCreate">确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import { createProject, deleteProject, getProjectList, updateProject } from '@/api/projectApi'
import { getProductList } from '@/api/productApi'
const getDefaultCreateForm = () => ({
id: undefined,
name: '',
productId: '',
status: 1,
description: ''
})
export default {
name: 'ProjectList',
components: { PageSection },
data() {
return {
loading: false,
createSubmitting: false,
createDialogVisible: false,
dialogMode: 'create',
queryForm: {
keyword: '',
status: ''
},
createForm: getDefaultCreateForm(),
createRules: {
name: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
productId: [{ required: true, message: '请选择所属产品', trigger: 'change' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
},
productOptions: [],
pageNo: 1,
pageSize: 10,
total: 0,
tableData: []
}
},
methods: {
fetchList() {
this.loading = true
getProjectList({
pageNo: this.pageNo,
pageSize: this.pageSize,
keyword: this.queryForm.keyword,
status: this.queryForm.status
}).then(res => {
const data = res && res.data ? res.data : res || {}
this.tableData = data.items || data.list || data.data || []
this.total = data.total || data.totalCount || this.tableData.length
}).catch(() => {
this.tableData = []
this.total = 0
}).finally(() => {
this.loading = false
})
},
handleSizeChange(val) {
this.pageSize = val
this.pageNo = 1
this.fetchList()
},
handleCurrentChange(val) {
this.pageNo = val
this.fetchList()
},
goSettings(row) {
this.$router.push({ path: '/test-platform/projects/settings', query: { projectId: row.id } })
},
fetchProductOptions() {
return getProductList({
pageNo: 1,
pageSize: 1000,
status: 1
}).then(res => {
const data = res && res.data ? res.data : res || {}
this.productOptions = data.items || data.list || data.data || []
}).catch(() => {
this.productOptions = []
})
},
openCreate() {
this.dialogMode = 'create'
this.createForm = getDefaultCreateForm()
this.fetchProductOptions()
this.createDialogVisible = true
this.$nextTick(() => {
if (this.$refs.createForm) {
this.$refs.createForm.clearValidate()
}
})
},
openEdit(row) {
this.dialogMode = 'edit'
this.fetchProductOptions()
this.createDialogVisible = true
this.$nextTick(() => {
this.createForm = Object.assign(getDefaultCreateForm(), row, {
id: row.id,
productId: row.productId || row.product_id || row.productId
})
if (this.$refs.createForm) {
this.$refs.createForm.clearValidate()
}
})
},
resetCreateForm() {
this.createForm = getDefaultCreateForm()
this.createSubmitting = false
this.$nextTick(() => {
if (this.$refs.createForm) {
this.$refs.createForm.resetFields()
}
})
},
submitCreate() {
this.$refs.createForm.validate(valid => {
if (!valid) {
return
}
this.createSubmitting = true
const request = this.dialogMode === 'create'
? createProject(this.createForm)
: updateProject(this.createForm)
request.then(res => {
const message = (res && res.message) || ''
if (res && res.code === 20000) {
this.$message.success(message || (this.dialogMode === 'create' ? '项目创建成功' : '项目更新成功'))
this.createDialogVisible = false
this.pageNo = 1
this.fetchList()
return
}
this.$message.error(message || (this.dialogMode === 'create' ? '项目创建失败' : '项目更新失败'))
}).finally(() => {
this.createSubmitting = false
})
})
},
handleDelete(row) {
this.$confirm('确认删除该项目吗?', '提示', {
type: 'warning'
}).then(() => {
deleteProject({ id: row.id }).then(() => {
this.$message.success('项目删除成功')
if (this.tableData.length === 1 && this.pageNo > 1) {
this.pageNo = this.pageNo - 1
}
this.fetchList()
})
}).catch(() => {})
}
},
created() {
this.fetchList()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,281 @@
<template>
<div class="page-wrap">
<page-section title="项目设置">
<el-tabs value="members">
<el-tab-pane label="项目成员" name="members">
<div class="toolbar-wrap">
<el-button type="primary" size="small" @click="openMemberDialog">新增成员</el-button>
</div>
<el-table :data="members" border>
<el-table-column prop="user_id" label="用户ID"></el-table-column>
<el-table-column prop="role" label="角色"></el-table-column>
<el-table-column prop="joined_at" label="加入时间"></el-table-column>
</el-table>
<div style="margin-top: 16px; text-align: right;">
<el-pagination
:current-page="memberPageNo"
:page-size="memberPageSize"
:page-sizes="[10, 20, 50, 100]"
:total="memberTotal"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleMemberSizeChange"
@current-change="handleMemberCurrentChange">
</el-pagination>
</div>
</el-tab-pane>
<el-tab-pane label="环境配置" name="environments">
<div class="toolbar-wrap">
<el-button type="primary" size="small" @click="openEnvironmentDialog">新增环境</el-button>
</div>
<el-table :data="environments" border>
<el-table-column prop="name" label="环境"></el-table-column>
<el-table-column prop="variables" label="变量">
<template slot-scope="scope">
<json-viewer :value="scope.row.variables"></json-viewer>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 16px; text-align: right;">
<el-pagination
:current-page="environmentPageNo"
:page-size="environmentPageSize"
:page-sizes="[10, 20, 50, 100]"
:total="environmentTotal"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleEnvironmentSizeChange"
@current-change="handleEnvironmentCurrentChange">
</el-pagination>
</div>
</el-tab-pane>
</el-tabs>
</page-section>
<el-dialog title="新增成员" :visible.sync="memberDialogVisible" width="520px" @close="resetMemberForm">
<el-form ref="memberForm" :model="memberForm" :rules="memberRules" label-width="94px" size="small">
<el-form-item label="用户ID" prop="user_id">
<el-input v-model.trim="memberForm.user_id" maxlength="64" placeholder="请输入用户ID"></el-input>
</el-form-item>
<el-form-item label="角色" prop="role">
<el-input v-model.trim="memberForm.role" maxlength="64" placeholder="请输入角色"></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button size="small" @click="memberDialogVisible = false">取消</el-button>
<el-button type="primary" size="small" :loading="memberSubmitting" @click="submitMember">确定</el-button>
</span>
</el-dialog>
<el-dialog title="新增环境" :visible.sync="environmentDialogVisible" width="520px" @close="resetEnvironmentForm">
<el-form ref="environmentForm" :model="environmentForm" :rules="environmentRules" label-width="94px" size="small">
<el-form-item label="环境名称" prop="name">
<el-input v-model.trim="environmentForm.name" maxlength="64" placeholder="请输入环境名称"></el-input>
</el-form-item>
<el-form-item label="变量JSON" prop="variablesText">
<el-input v-model.trim="environmentForm.variablesText" type="textarea" :rows="6" placeholder='请输入 JSON例如 {"baseUrl":"https://test.com"}'></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button size="small" @click="environmentDialogVisible = false">取消</el-button>
<el-button type="primary" size="small" :loading="environmentSubmitting" @click="submitEnvironment">确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import JsonViewer from '@/components/TestPlatform/common/JsonViewer'
import { createEnvironment, createProjectMember, getProjectEnvironments, getProjectMembers } from '@/api/projectApi'
const getDefaultMemberForm = () => ({
user_id: '',
role: ''
})
const getDefaultEnvironmentForm = () => ({
name: '',
variablesText: '{}'
})
export default {
name: 'ProjectSettings',
components: { PageSection, JsonViewer },
data() {
return {
memberPageNo: 1,
memberPageSize: 10,
memberTotal: 0,
environmentPageNo: 1,
environmentPageSize: 10,
environmentTotal: 0,
members: [],
environments: [],
memberDialogVisible: false,
environmentDialogVisible: false,
memberSubmitting: false,
environmentSubmitting: false,
memberForm: getDefaultMemberForm(),
environmentForm: getDefaultEnvironmentForm(),
memberRules: {
user_id: [{ required: true, message: '请输入用户ID', trigger: 'blur' }],
role: [{ required: true, message: '请输入角色', trigger: 'blur' }]
},
environmentRules: {
name: [{ required: true, message: '请输入环境名称', trigger: 'blur' }],
variablesText: [{ required: true, message: '请输入变量JSON', trigger: 'blur' }]
}
}
},
methods: {
getProjectId() {
return this.$route.query.projectId || 1
},
fetchData() {
const projectId = this.getProjectId()
getProjectMembers(projectId, {
pageNo: this.memberPageNo,
pageSize: this.memberPageSize
}).then(res => {
const data = (res && res.data) || res || []
this.members = data.items || data.list || data.data || data || []
this.memberTotal = data.total || data.totalCount || this.members.length
}).catch(() => {
this.members = []
this.memberTotal = 0
})
getProjectEnvironments(projectId, {
pageNo: this.environmentPageNo,
pageSize: this.environmentPageSize
}).then(res => {
const data = (res && res.data) || res || []
this.environments = data.items || data.list || data.data || data || []
this.environmentTotal = data.total || data.totalCount || this.environments.length
}).catch(() => {
this.environments = []
this.environmentTotal = 0
})
},
openMemberDialog() {
this.memberDialogVisible = true
this.$nextTick(() => {
this.memberForm = getDefaultMemberForm()
if (this.$refs.memberForm) {
this.$refs.memberForm.clearValidate()
}
})
},
resetMemberForm() {
this.memberForm = getDefaultMemberForm()
this.memberSubmitting = false
this.$nextTick(() => {
if (this.$refs.memberForm) {
this.$refs.memberForm.resetFields()
}
})
},
submitMember() {
this.$refs.memberForm.validate(valid => {
if (!valid) {
return
}
this.memberSubmitting = true
createProjectMember(Object.assign({ project_id: this.getProjectId() }, this.memberForm)).then(res => {
const message = (res && res.message) || ''
if (res && res.code === 20000) {
this.$message.success(message || '成员新增成功')
this.memberDialogVisible = false
this.memberPageNo = 1
this.fetchData()
return
}
this.$message.error(message || '成员新增失败')
}).finally(() => {
this.memberSubmitting = false
})
})
},
openEnvironmentDialog() {
this.environmentDialogVisible = true
this.$nextTick(() => {
this.environmentForm = getDefaultEnvironmentForm()
if (this.$refs.environmentForm) {
this.$refs.environmentForm.clearValidate()
}
})
},
resetEnvironmentForm() {
this.environmentForm = getDefaultEnvironmentForm()
this.environmentSubmitting = false
this.$nextTick(() => {
if (this.$refs.environmentForm) {
this.$refs.environmentForm.resetFields()
}
})
},
submitEnvironment() {
this.$refs.environmentForm.validate(valid => {
if (!valid) {
return
}
let variables = {}
try {
variables = JSON.parse(this.environmentForm.variablesText || '{}')
} catch (e) {
this.$message.error('变量JSON格式不正确')
return
}
this.environmentSubmitting = true
createEnvironment({
project_id: this.getProjectId(),
name: this.environmentForm.name,
variables
}).then(res => {
const message = (res && res.message) || ''
if (res && res.code === 20000) {
this.$message.success(message || '环境新增成功')
this.environmentDialogVisible = false
this.environmentPageNo = 1
this.fetchData()
return
}
this.$message.error(message || '环境新增失败')
}).finally(() => {
this.environmentSubmitting = false
})
})
},
handleMemberSizeChange(val) {
this.memberPageSize = val
this.memberPageNo = 1
this.fetchData()
},
handleMemberCurrentChange(val) {
this.memberPageNo = val
this.fetchData()
},
handleEnvironmentSizeChange(val) {
this.environmentPageSize = val
this.environmentPageNo = 1
this.fetchData()
},
handleEnvironmentCurrentChange(val) {
this.environmentPageNo = val
this.fetchData()
}
},
created() {
this.fetchData()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
.toolbar-wrap {
margin-bottom: 16px;
text-align: right;
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<div class="page-wrap">
<page-section title="测试报告">
<template slot="extra">
<el-button type="primary" size="small" :loading="generating" @click="handleGenerate">生成报告</el-button>
</template>
<el-form :inline="true" :model="queryForm" size="small">
<el-form-item label="项目ID">
<el-input v-model="projectId" style="width: 120px;"></el-input>
</el-form-item>
<el-form-item label="计划ID">
<el-input v-model="queryForm.plan_id" style="width: 120px;"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchList">查询</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="tableData" border style="margin-top: 16px;">
<el-table-column prop="name" label="报告名称" min-width="180"></el-table-column>
<el-table-column prop="report_type" label="类型" width="120"></el-table-column>
<el-table-column prop="generated_at" label="生成时间" min-width="160"></el-table-column>
<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button type="text" @click="goViewer(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 16px; text-align: right;">
<el-pagination
:current-page="pageNo"
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange">
</el-pagination>
</div>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import { generateReport, getReportList } from '@/api/reportApi'
export default {
name: 'ReportList',
components: { PageSection },
data() {
return {
loading: false,
generating: false,
projectId: this.$route.query.projectId || 1,
queryForm: {
plan_id: this.$route.query.planId || ''
},
tableData: []
}
},
methods: {
fetchList() {
this.loading = true
getReportList(this.projectId, Object.assign({}, this.queryForm, {
pageNo: this.pageNo,
pageSize: this.pageSize
})).then(res => {
const data = (res && res.data) || res || {}
this.tableData = data.items || data.list || data.data || []
this.total = data.total || data.totalCount || this.tableData.length
}).catch(() => {
this.tableData = []
this.total = 0
}).finally(() => {
this.loading = false
})
},
handleGenerate() {
if (!this.queryForm.plan_id) {
this.$message({ type: 'warning', message: '请先输入计划ID' })
return
}
this.generating = true
generateReport(this.projectId, { plan_id: this.queryForm.plan_id }).then(() => {
this.$message({ type: 'success', message: '报告生成任务已提交' })
}).finally(() => {
this.generating = false
})
},
goViewer(row) {
this.$router.push({ path: '/test-platform/reports/viewer', query: { projectId: this.projectId, reportId: row.id } })
}
},
created() {
this.fetchList()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
</style>

View File

@@ -0,0 +1,58 @@
<template>
<div class="page-wrap">
<page-section title="报告查看">
<el-alert
v-if="!report.content"
title="当前无可展示内容,待后端返回 report.content HTML"
type="info"
:closable="false"
style="margin-bottom: 16px;">
</el-alert>
<div class="report-html" v-html="report.content || ''"></div>
<el-divider></el-divider>
<json-viewer :value="report.summary || {}"></json-viewer>
</page-section>
</div>
</template>
<script>
import PageSection from '@/components/TestPlatform/common/PageSection'
import JsonViewer from '@/components/TestPlatform/common/JsonViewer'
import { getReportDetail } from '@/api/reportApi'
export default {
name: 'ReportViewer',
components: { PageSection, JsonViewer },
data() {
return {
projectId: this.$route.query.projectId || 1,
reportId: this.$route.query.reportId || '',
report: {}
}
},
methods: {
fetchDetail() {
if (!this.reportId) {
return
}
getReportDetail(this.projectId, this.reportId).then(res => {
this.report = (res && res.data) || res || {}
}).catch(() => {
this.report = {}
})
}
},
created() {
this.fetchDetail()
}
}
</script>
<style scoped>
.page-wrap {
padding: 20px;
}
.report-html {
min-height: 200px;
}
</style>

View File

@@ -0,0 +1,40 @@
<template>
<pre class="json-viewer">{{ formattedValue }}</pre>
</template>
<script>
export default {
name: 'JsonViewer',
props: {
value: {
type: [Object, Array, String, Number, Boolean],
default: () => ({})
}
},
computed: {
formattedValue() {
if (typeof this.value === 'string') {
try {
return JSON.stringify(JSON.parse(this.value), null, 2)
} catch (e) {
return this.value
}
}
return JSON.stringify(this.value || {}, null, 2)
}
}
}
</script>
<style scoped>
.json-viewer {
margin: 0;
padding: 16px;
background: #0f172a;
color: #e2e8f0;
border-radius: 6px;
white-space: pre-wrap;
word-break: break-all;
line-height: 1.6;
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<el-descriptions :column="column" border>
<el-descriptions-item
v-for="item in items"
:key="item.label"
:label="item.label">
{{ formatValue(item.value) }}
</el-descriptions-item>
</el-descriptions>
</template>
<script>
export default {
name: 'KeyValueDescriptions',
props: {
items: {
type: Array,
default: () => []
},
column: {
type: Number,
default: 2
}
},
methods: {
formatValue(value) {
if (value === null || typeof value === 'undefined' || value === '') {
return '-'
}
if (typeof value === 'object') {
return JSON.stringify(value)
}
return String(value)
}
}
}
</script>

View File

@@ -0,0 +1,39 @@
<template>
<el-card shadow="never" class="page-section">
<div slot="header" class="section-header">
<span>{{ title }}</span>
<div v-if="$slots.extra" class="section-extra">
<slot name="extra"></slot>
</div>
</div>
<div class="section-body">
<slot></slot>
</div>
</el-card>
</template>
<script>
export default {
name: 'PageSection',
props: {
title: {
type: String,
default: ''
}
}
}
</script>
<style scoped>
.page-section {
margin-bottom: 16px;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.section-extra {
margin-left: 16px;
}
</style>