新增设计需求、测试项、用例批量新增页面和功能
This commit is contained in:
624
cdTMP/package-lock.json
generated
624
cdTMP/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@
|
||||
"dependencies": {
|
||||
"@arco-design/color": "^0.4.0",
|
||||
"@arco-design/web-vue": "^2.57.0",
|
||||
"@tanstack/vue-query": "^5.92.0",
|
||||
"@tanstack/vue-query": "^5.92.1",
|
||||
"@tinymce/tinymce-vue": "^6.3.0",
|
||||
"@vueuse/core": "^14.1.0",
|
||||
"axios": "^1.13.2",
|
||||
@@ -21,45 +21,45 @@
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"file2md5": "^1.3.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lodash-es": "^4.17.22",
|
||||
"mammoth": "^1.11.0",
|
||||
"mitt": "^3.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^3.0.4",
|
||||
"pinyin-match": "^1.2.9",
|
||||
"pinyin-match": "^1.2.10",
|
||||
"postcss-import": "^16.1.1",
|
||||
"qs": "^6.14.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tinymce": "^7.9.1",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"vue": "^3.5.25",
|
||||
"vue": "^3.5.26",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-color-kit": "^1.0.6",
|
||||
"vue-data-ui": "^3.7.15",
|
||||
"vue-router": "^4.6.3",
|
||||
"vue-data-ui": "^3.9.6",
|
||||
"vue-router": "^4.6.4",
|
||||
"vuedraggable": "^2.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/node": "^25.0.3",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/qs": "^6.14.0",
|
||||
"@vitejs/plugin-vue": "^6.0.2",
|
||||
"@vitejs/plugin-vue": "^6.0.3",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.2",
|
||||
"@vue/babel-plugin-jsx": "^2.0.1",
|
||||
"browserslist": "^4.28.1",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-plugin-vue": "^10.6.2",
|
||||
"less": "^4.4.2",
|
||||
"less": "^4.5.1",
|
||||
"less-loader": "^12.3.0",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.7.4",
|
||||
"rollup-plugin-visualizer": "^6.0.5",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.2.6",
|
||||
"vite": "^7.3.0",
|
||||
"vue-eslint-parser": "^10.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,9 @@ export default {
|
||||
* @returns 可流式或一次性
|
||||
*/
|
||||
getAiTestItem(data: DataRowType) {
|
||||
if (import.meta.env.DEV) {
|
||||
return request({
|
||||
url: `/local_doc_qa/testing_item`,
|
||||
timeout: 20000,
|
||||
method: "post",
|
||||
data
|
||||
})
|
||||
}
|
||||
return request({
|
||||
url: `${AI_API_BASE}/api/local_doc_qa/testing_item`,
|
||||
timeout: 20000,
|
||||
url: import.meta.env.DEV ? `/local_doc_qa/testing_item` : `${AI_API_BASE}/api/local_doc_qa/testing_item`,
|
||||
timeout: 120000,
|
||||
method: "post",
|
||||
data
|
||||
})
|
||||
|
||||
@@ -44,6 +44,17 @@ export default {
|
||||
data: params
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 添加测试用例
|
||||
* @returns
|
||||
*/
|
||||
batchSave(data = {}) {
|
||||
return request({
|
||||
url: "/project/case/multi_save",
|
||||
method: "post",
|
||||
data
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 更新测试用例
|
||||
* @returns
|
||||
|
||||
@@ -59,11 +59,11 @@ export default {
|
||||
* 批量添加设计需求
|
||||
* @returns
|
||||
*/
|
||||
multiSave(params = {}) {
|
||||
multiSave(data = {}) {
|
||||
return request({
|
||||
url: "/project/designDemand/multi_save",
|
||||
method: "post",
|
||||
data: params
|
||||
data
|
||||
})
|
||||
},
|
||||
/**
|
||||
@@ -109,5 +109,16 @@ export default {
|
||||
method: "post",
|
||||
data
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 自动生成人机交互界面测试5个测试项和用例
|
||||
* @returns
|
||||
*/
|
||||
create_rj(params = {}) {
|
||||
return request({
|
||||
url: "/project/create_renji/",
|
||||
method: "get",
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export default {
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 根据项目名、树节点等级和key查找设计需求
|
||||
* 根据项目名、树节点等级和key查找测试项
|
||||
* @returns 设计需求树状节点信息
|
||||
*/
|
||||
getDemandInfo(projectId, key, level) {
|
||||
@@ -42,8 +42,22 @@ export default {
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 根据项目名、树节点等级和key查找测试需求
|
||||
* @returns 返回测试需求testDemand
|
||||
* 根据项目id和轮次key
|
||||
* @returns 测试项级联数据
|
||||
*/
|
||||
getRoundRelatedDemand(id, round) {
|
||||
return request({
|
||||
url: `project/getRelatedTestDemand`,
|
||||
method: "get",
|
||||
params: {
|
||||
id,
|
||||
round
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 根据项目名、树节点等级和key查找测试项
|
||||
* @returns 返回测试项testDemand
|
||||
*/
|
||||
getTestInfo(projectId, key, level) {
|
||||
return request({
|
||||
|
||||
@@ -43,6 +43,17 @@ export default {
|
||||
data: params
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 批量新增添加测试项
|
||||
* @returns
|
||||
*/
|
||||
batchSave(data = {}) {
|
||||
return request({
|
||||
url: "/project/testDemand/multi_save",
|
||||
method: "post",
|
||||
data
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 更新dut
|
||||
* @returns
|
||||
|
||||
@@ -59,8 +59,7 @@ const props = defineProps({
|
||||
toolbar: {
|
||||
type: [String, Array],
|
||||
// 如果要取消粘贴只粘贴文本,需要用户加格式请加上pastetext
|
||||
default:
|
||||
"code undo redo aligncenter alignleft indent styleselect formatselect fontselect fontsizeselect removeformat"
|
||||
default: "code undo redo aligncenter alignleft indent styleselect formatselect fontselect fontsizeselect removeformat"
|
||||
|
||||
// 下面是备份配置:
|
||||
// default:"code undo redo restoredraft | paste | bold | aligncenter alignleft alignjustify indent searchreplace | \
|
||||
@@ -154,10 +153,7 @@ const initConfig = reactive({
|
||||
min_height: 100,
|
||||
max_height: 600,
|
||||
autoresize_bottom_margin: 10,
|
||||
content_css:
|
||||
theme.value === "dark"
|
||||
? "/tinymce/skins/content/dark/content.css"
|
||||
: "/tinymce/skins/content/default/content.css",
|
||||
content_css: theme.value === "dark" ? "/tinymce/skins/content/dark/content.css" : "/tinymce/skins/content/default/content.css",
|
||||
// selector: "#textarea1", // 下面自定义样式选中的区域为编辑区
|
||||
content_style: "body {line-height:1.5;font-size:14px;} p {margin:2px 0px;}", // 这里可以设置自定义样式
|
||||
// paste_as_text: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="prop.width"
|
||||
:fullscreen="isFull"
|
||||
:fullscreen="prop.isFull"
|
||||
v-model:visible="modal.visible"
|
||||
:on-before-ok="modal.submit"
|
||||
unmount-on-close
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
<!--
|
||||
- @Author XXX
|
||||
- @Link XXX
|
||||
-->
|
||||
<template>
|
||||
<ma-form-item
|
||||
v-if="typeof props.component.display == 'undefined' || props.component.display === true"
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
<template>
|
||||
<div class="w-full" ref="containerRef" id="form-main-id">
|
||||
<a-spin :loading="formLoading" :tip="options.loadingText" class="w-full ma-form-spin">
|
||||
<div
|
||||
v-if="options.showFormTitle"
|
||||
:class="['ma-form-title', options.formTitleClass]"
|
||||
:style="options.formTitleStyle"
|
||||
>
|
||||
<div v-if="options.showFormTitle" :class="['ma-form-title', options.formTitleClass]" :style="options.formTitleStyle">
|
||||
{{ options.formTitle }}
|
||||
</div>
|
||||
<a-form
|
||||
@@ -37,23 +33,13 @@
|
||||
<a-space>
|
||||
<slot name="formBeforeButtons" />
|
||||
<slot name="formButtons">
|
||||
<a-button
|
||||
:type="options.submitType"
|
||||
:status="options.submitStatus"
|
||||
v-if="options.submitShowBtn"
|
||||
html-type="submit"
|
||||
>
|
||||
<a-button :type="options.submitType" :status="options.submitStatus" v-if="options.submitShowBtn" html-type="submit">
|
||||
<template #icon v-if="options?.submitIcon">
|
||||
<component :is="options.submitIcon" />
|
||||
</template>
|
||||
{{ options.submitText }}
|
||||
</a-button>
|
||||
<a-button
|
||||
:type="options.resetType"
|
||||
:status="options.resetStatus"
|
||||
v-if="options.resetShowBtn"
|
||||
@click="resetForm"
|
||||
>
|
||||
<a-button :type="options.resetType" :status="options.resetStatus" v-if="options.resetShowBtn" @click="resetForm">
|
||||
<template #icon v-if="options?.resetIcon">
|
||||
<component :is="options.resetIcon" />
|
||||
</template>
|
||||
@@ -90,14 +76,7 @@
|
||||
import { ref, watch, provide, onMounted, onUnmounted, nextTick, getCurrentInstance, inject, computed } from "vue"
|
||||
import { isNil, set, get, cloneDeep } from "lodash-es"
|
||||
import defaultOptions from "./js/defaultOptions.js"
|
||||
import {
|
||||
getComponentName,
|
||||
toHump,
|
||||
interactiveControl,
|
||||
handleFlatteningColumns,
|
||||
insertGlobalCssToHead,
|
||||
insertGlobalFunctionsToHtml
|
||||
} from "./js/utils.js"
|
||||
import { getComponentName, toHump, interactiveControl, handleFlatteningColumns, insertGlobalCssToHead, insertGlobalFunctionsToHtml } from "./js/utils.js"
|
||||
import { loadDict, handlerCascader } from "./js/networkRequest.js"
|
||||
import arrayComponentDefault from "./js/defaultArrayComponent.js"
|
||||
import ColumnService from "./js/columnService.js"
|
||||
@@ -150,14 +129,9 @@ import ParentPreview from "@/views/project/ParentPreview/index.vue"
|
||||
// 判断是否有
|
||||
const formKey = computed(() => {
|
||||
// 去掉双击被测件:即key.split("").length > 1
|
||||
if (
|
||||
form.value.key &&
|
||||
typeof form.value.key !== "number" &&
|
||||
form.value.key &&
|
||||
form.value.key.split("-").length > 2
|
||||
) {
|
||||
if (form.value.key && typeof form.value.key !== "number" && form.value.key && form.value.key.split("-").length > 2) {
|
||||
// 如果存在则取前面的
|
||||
return form.value.key.slice(0, -2)
|
||||
return form.value.key.split("-").slice(0, -1).join("-")
|
||||
}
|
||||
return ""
|
||||
})
|
||||
@@ -257,14 +231,7 @@ const init = async () => {
|
||||
}
|
||||
|
||||
// 联动
|
||||
await handlerCascader(
|
||||
get(form.value, item.dataIndex),
|
||||
item,
|
||||
flatteningColumns.value,
|
||||
dictList.value,
|
||||
form.value,
|
||||
false
|
||||
)
|
||||
await handlerCascader(get(form.value, item.dataIndex), item, flatteningColumns.value, dictList.value, form.value, false)
|
||||
})
|
||||
|
||||
await nextTick(() => {
|
||||
|
||||
@@ -11,11 +11,7 @@
|
||||
:label-style="props.labelStyle"
|
||||
:value-style="props.valueStyle"
|
||||
>
|
||||
<a-descriptions-item
|
||||
v-for="item in descriptions"
|
||||
:label="item.label"
|
||||
:span="item.span ? item.span : isArray(item.value) ? props.column : 1"
|
||||
>
|
||||
<a-descriptions-item v-for="item in descriptions" :label="item.label" :span="item.span ? item.span : isArray(item.value) ? props.column : 1">
|
||||
<template v-if="item.formType === 'upload'">
|
||||
<a-image-preview-group infinite v-if="isArray(item.value)">
|
||||
<a-space>
|
||||
@@ -34,11 +30,10 @@
|
||||
<template v-for="(sub, idx) in item.value" :key="idx">
|
||||
<!-- 这是每个测试子项 -->
|
||||
<div class="subTitle mt-1">{{ idx + 1 }}.{{ sub.subName }}</div>
|
||||
<div>测试子项描述:{{ sub.subDescription }}</div>
|
||||
<template v-for="(step, index) in sub.subStep" :key="index">
|
||||
<span class="text-amber-700">步骤{{ index + 1 }})</span>
|
||||
<div class="operation">
|
||||
<span class="text-bold">操作:</span>{{ step.operation }}
|
||||
</div>
|
||||
<div class="operation"><span class="text-bold">操作:</span>{{ step.operation }}</div>
|
||||
<div class="mb-1"><span class="text-bold">预期:</span>{{ step.expect }}</div>
|
||||
</template>
|
||||
</template>
|
||||
@@ -46,9 +41,7 @@
|
||||
<template v-else>暂无信息</template>
|
||||
</div>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="item.formType === 'radio' || item.formType === 'select' || item.formType === 'checkbox'"
|
||||
>
|
||||
<template v-else-if="item.formType === 'radio' || item.formType === 'select' || item.formType === 'checkbox'">
|
||||
<template v-if="isArray(item.value)">
|
||||
<!-- 修改源码 -->
|
||||
<a-space>
|
||||
@@ -162,6 +155,8 @@ defineExpose({ reset })
|
||||
|
||||
<style scoped>
|
||||
.white-space-change {
|
||||
max-height: 68vh;
|
||||
white-space: normal;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,112 +3,115 @@
|
||||
- @Link XXX
|
||||
-->
|
||||
<template>
|
||||
<div class="flex flex-col w-full h-full">
|
||||
<a-input-group class="mb-2 w-full" size="mini">
|
||||
<a-input
|
||||
:placeholder="props?.searchPlaceholder"
|
||||
allow-clear
|
||||
@input="changeKeyword"
|
||||
@clear="resetData"
|
||||
/>
|
||||
<a-button
|
||||
@click="() => {
|
||||
isExpand ? maTree.expandAll(false) : maTree.expandAll(true)
|
||||
isExpand = ! isExpand
|
||||
}"
|
||||
>{{ isExpand ? '折叠' : '展开' }}</a-button>
|
||||
</a-input-group>
|
||||
<a-tree
|
||||
blockNode
|
||||
ref="maTree"
|
||||
:data="treeData"
|
||||
class="h-full w-full"
|
||||
@select="handlerSelect"
|
||||
:field-names="props.fieldNames"
|
||||
v-model:selected-keys="modelValue"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template #icon v-if="props.icon"><component :is="props.icon" /></template>
|
||||
</a-tree>
|
||||
</div>
|
||||
<div class="flex flex-col w-full h-full">
|
||||
<a-input-group class="mb-2 w-full" size="mini">
|
||||
<a-input :placeholder="props?.searchPlaceholder" allow-clear @input="changeKeyword" @clear="resetData" />
|
||||
<a-button
|
||||
@click="
|
||||
() => {
|
||||
isExpand ? maTree.expandAll(false) : maTree.expandAll(true)
|
||||
isExpand = !isExpand
|
||||
}
|
||||
"
|
||||
>{{ isExpand ? "折叠" : "展开" }}</a-button
|
||||
>
|
||||
</a-input-group>
|
||||
<a-tree
|
||||
blockNode
|
||||
ref="maTree"
|
||||
:data="treeData"
|
||||
class="h-full w-full"
|
||||
@select="handlerSelect"
|
||||
:field-names="props.fieldNames"
|
||||
v-model:selected-keys="modelValue"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template #icon v-if="props.icon"><component :is="props.icon" /></template>
|
||||
</a-tree>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { ref, watch, computed } from "vue"
|
||||
|
||||
const treeData = ref([])
|
||||
const maTree = ref()
|
||||
const isExpand = ref(false)
|
||||
const treeData = ref([])
|
||||
const maTree = ref()
|
||||
const isExpand = ref(false)
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'click'])
|
||||
const emit = defineEmits(["update:modelValue", "click"])
|
||||
|
||||
const props = defineProps({
|
||||
const props = defineProps({
|
||||
modelValue: { type: Array },
|
||||
data: { type: Array },
|
||||
searchPlaceholder: { type: String },
|
||||
fieldNames: { type: Object, default: () => { return { title: 'label', key: 'value' } } },
|
||||
icon: { type: String, default: undefined },
|
||||
})
|
||||
fieldNames: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return { title: "label", key: "value" }
|
||||
}
|
||||
},
|
||||
icon: { type: String, default: undefined }
|
||||
})
|
||||
|
||||
const modelValue = computed({
|
||||
const modelValue = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
return props.modelValue
|
||||
},
|
||||
set(newVal) {
|
||||
emit('update:modelValue', newVal)
|
||||
emit("update:modelValue", newVal)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
watch(
|
||||
watch(
|
||||
() => props.data,
|
||||
val => {
|
||||
treeData.value = val
|
||||
(val) => {
|
||||
treeData.value = val
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
)
|
||||
|
||||
const handlerSelect = (item, data) => {
|
||||
modelValue.value = [ item ]
|
||||
emit('click', ...[item, data])
|
||||
}
|
||||
const handlerSelect = (item, data) => {
|
||||
modelValue.value = [item]
|
||||
emit("click", ...[item, data])
|
||||
}
|
||||
|
||||
const resetData = () => treeData.value = props.data
|
||||
const resetData = () => (treeData.value = props.data)
|
||||
|
||||
const changeKeyword = (keyword) => {
|
||||
if (!keyword || keyword === '') {
|
||||
treeData.value = Object.assign(props.data, [])
|
||||
return false
|
||||
const changeKeyword = (keyword) => {
|
||||
if (!keyword || keyword === "") {
|
||||
treeData.value = Object.assign(props.data, [])
|
||||
return false
|
||||
}
|
||||
treeData.value = searchNode(keyword)
|
||||
}
|
||||
}
|
||||
|
||||
const searchNode = (keyword) => {
|
||||
const searchNode = (keyword) => {
|
||||
const loop = (data) => {
|
||||
let tree = []
|
||||
data.map(item => {
|
||||
if (item.children && item.children.length > 0) {
|
||||
const temp = loop(item.children)
|
||||
tree.push(...temp)
|
||||
} else if (item[props.fieldNames['title']].indexOf(keyword) !== -1) {
|
||||
tree.push(item)
|
||||
}
|
||||
return tree
|
||||
})
|
||||
let tree = []
|
||||
data.map((item) => {
|
||||
if (item.children && item.children.length > 0) {
|
||||
const temp = loop(item.children)
|
||||
tree.push(...temp)
|
||||
} else if (item[props.fieldNames["title"]].indexOf(keyword) !== -1) {
|
||||
tree.push(item)
|
||||
}
|
||||
return tree
|
||||
})
|
||||
|
||||
return tree
|
||||
return tree
|
||||
}
|
||||
return loop(treeData.value)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ maTree })
|
||||
defineExpose({ maTree })
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-tree-node:hover) {
|
||||
background-color: var(--color-fill-2);
|
||||
border-radius: 3px;
|
||||
background-color: var(--color-fill-2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
:deep(.arco-tree-node-switcher) {
|
||||
margin-left: 2px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,6 +8,6 @@ interface ITextInfo {
|
||||
const textInfo: ITextInfo = {
|
||||
testUnitAddDefaultText: "中国科学院卫星软件测评中心",
|
||||
testUnitContactPersonName: "高才栋",
|
||||
testUnintContactPhoneNumber: "13564753024"
|
||||
testUnintContactPhoneNumber: "010-50735018"
|
||||
}
|
||||
export default textInfo
|
||||
|
||||
@@ -48,69 +48,30 @@
|
||||
<template v-if="nodeData.level === '0'">
|
||||
<a-tooltip content="点击新增轮次">
|
||||
<IconPlus
|
||||
style="
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
font-size: 12px;
|
||||
top: 8px;
|
||||
color: #3370ff;
|
||||
"
|
||||
style="position: absolute; right: 8px; font-size: 12px; top: 8px; color: #3370ff"
|
||||
@click="() => handleRoundAddClick(nodeData)"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip content="点击删除轮次">
|
||||
<a-popconfirm
|
||||
content="确定要删除该轮次吗?"
|
||||
position="bottom"
|
||||
@ok="handleRoundDelClick(nodeData)"
|
||||
>
|
||||
<IconMinus
|
||||
style="
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
font-size: 12px;
|
||||
top: 8px;
|
||||
color: #3370ff;
|
||||
"
|
||||
/>
|
||||
<a-popconfirm content="确定要删除该轮次吗?" position="bottom" @ok="handleRoundDelClick(nodeData)">
|
||||
<icon-close style="position: absolute; right: 25px; font-size: 12px; top: 8px; color: #3370ff" />
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
<a-tooltip content="点击编辑当前轮次"
|
||||
><IconEdit
|
||||
style="
|
||||
position: absolute;
|
||||
right: 42px;
|
||||
font-size: 12px;
|
||||
top: 8px;
|
||||
color: #3370ff;
|
||||
"
|
||||
style="position: absolute; right: 42px; font-size: 12px; top: 8px; color: #3370ff"
|
||||
@click="() => handleRoundEditClick(nodeData)"
|
||||
/></a-tooltip>
|
||||
<a-tooltip content="综合阅览该轮次下问题单"
|
||||
><icon-bug
|
||||
style="
|
||||
position: absolute;
|
||||
right: 60px;
|
||||
font-size: 12px;
|
||||
top: 8px;
|
||||
color: #3370ff;
|
||||
"
|
||||
style="position: absolute; right: 60px; font-size: 12px; top: 8px; color: #3370ff"
|
||||
@click="handleProblemShowClick"
|
||||
/></a-tooltip>
|
||||
<a-tooltip content="轮次数据管理视图"
|
||||
><icon-storage
|
||||
style="
|
||||
position: absolute;
|
||||
right: 77px;
|
||||
font-size: 12px;
|
||||
top: 8px;
|
||||
color: #3370ff;
|
||||
"
|
||||
style="position: absolute; right: 77px; font-size: 12px; top: 8px; color: #3370ff"
|
||||
:style="{
|
||||
color:
|
||||
isOpeSetsRoute && nodeData.key === route.query.key
|
||||
? 'red'
|
||||
: '#3370ff'
|
||||
color: isOpeSetsRoute && nodeData.key === route.query.key ? 'red' : '#3370ff'
|
||||
}"
|
||||
@click="
|
||||
() => {
|
||||
@@ -127,13 +88,15 @@
|
||||
</template>
|
||||
</template>
|
||||
<!-- 设计节点的图标 -->
|
||||
<template #switcher-icon="node, { isLeaf }">
|
||||
<IconDown v-if="!isLeaf" />
|
||||
<IconFile v-if="isLeaf" />
|
||||
<template #switcher-icon="_, { isLeaf }">
|
||||
<div class="font-icon">
|
||||
<IconDownCircle v-if="!isLeaf" />
|
||||
<IconFile v-if="isLeaf" />
|
||||
</div>
|
||||
</template>
|
||||
<!-- 节点图标插槽 -->
|
||||
<template #icon="props">
|
||||
<template v-if="props.node.level === '1'"> [被测件] </template>
|
||||
<template v-if="props.node.level === '1'"> <span style="white-space: nowrap">[被测件]</span> </template>
|
||||
<template v-if="props.node.level === '2'"> [设] </template>
|
||||
<template v-if="props.node.level === '3'"> [项] </template>
|
||||
<template v-if="props.node.level === '4'">
|
||||
@@ -177,15 +140,7 @@
|
||||
</a-button>
|
||||
</a-popover>
|
||||
</a-back-top>
|
||||
<ma-form-modal
|
||||
ref="maFormModalRef"
|
||||
:title="title"
|
||||
:column="roundColumn"
|
||||
:options="roundOption"
|
||||
:width="800"
|
||||
:submit="handleRoundSubmit"
|
||||
>
|
||||
</ma-form-modal>
|
||||
<ma-form-modal ref="maFormModalRef" :title="title" :column="roundColumn" :options="roundOption" :width="800" :submit="handleRoundSubmit"> </ma-form-modal>
|
||||
<!-- 如果没有第一轮的SO_dut则弹窗 -->
|
||||
<ma-form-modal
|
||||
ref="soDutFormRef"
|
||||
@@ -202,20 +157,9 @@
|
||||
<!-- 添加input前缀 -->
|
||||
<template #inputPrepend-version> V </template>
|
||||
</ma-form-modal>
|
||||
<Progress
|
||||
:visible="visible"
|
||||
:isComplete="isComplete"
|
||||
:text="ptext"
|
||||
@clickConfirm="handleModalConfirmClick"
|
||||
></Progress>
|
||||
<Progress :visible="visible" :isComplete="isComplete" :text="ptext" @clickConfirm="handleModalConfirmClick"></Progress>
|
||||
<!-- 右键菜单 -->
|
||||
<a-dropdown
|
||||
v-model:popup-visible="popupVisible"
|
||||
:popup-container="popupContainer"
|
||||
position="bottom"
|
||||
alignPoint
|
||||
:style="{ display: 'block' }"
|
||||
>
|
||||
<a-dropdown v-model:popup-visible="popupVisible" :popup-container="popupContainer" position="bottom" alignPoint :style="{ display: 'block' }">
|
||||
<template #content>
|
||||
<a-dgroup title="执行操作">
|
||||
<a-doption @click="handleDoptionClickGreateCases">
|
||||
@@ -278,6 +222,7 @@
|
||||
@update:visible="roundRightVisible = false"
|
||||
:container="roundRightContainer"
|
||||
@click-problem-show="handleProblemShowClick"
|
||||
@create-renji="handleCreateRenji"
|
||||
></roundRight>
|
||||
<!-- w2:轮次的问题单ma-crud,这里要传参2个,首先是请求另外一个接口,然后取消是否关联字段 -->
|
||||
<problem-choose ref="problemRoundRef" hasRelated="roundProblem" :title="problemTitle"></problem-choose>
|
||||
@@ -340,21 +285,13 @@ provide("toggleDrawerMenu", () => {
|
||||
const { searchKey, handleSearchTreeDataClick } = useSearchNodes()
|
||||
|
||||
//~~~~~~M功能:强制必须有源代码被测件~~~~~~
|
||||
const {
|
||||
soDutFormRef,
|
||||
soDutModalTitle,
|
||||
handleSoDutExistsForceModal,
|
||||
handleSoDutSubmit,
|
||||
handleSoDutCancel,
|
||||
soDutColumn
|
||||
} = useMustSoDut(projectId)
|
||||
const { soDutFormRef, soDutModalTitle, handleSoDutExistsForceModal, handleSoDutSubmit, handleSoDutCancel, soDutColumn } = useMustSoDut(projectId)
|
||||
|
||||
//~~~~~~小功能:展开和收缩~~~~~~
|
||||
const { expandedKeys, toggleExpanded } = useNodeExpand()
|
||||
|
||||
//~~~~~~大功能:单击/双击节点逻辑~~~~~~
|
||||
const { selectedKeys, pointNode, dutSubFormRef, designSubFormRef, testDemandSubFormRef, caseSubFormRef } =
|
||||
useNodeClick(expandedKeys)
|
||||
const { selectedKeys, pointNode, dutSubFormRef, designSubFormRef, testDemandSubFormRef, caseSubFormRef } = useNodeClick(expandedKeys)
|
||||
|
||||
//~~~~~~小功能:路由中key绑定selectedKeys:好像有点非性能~~~~~~
|
||||
const isOpeSetsRoute = ref(false)
|
||||
@@ -375,16 +312,10 @@ watch(
|
||||
const { loadMore } = useLoadTreeNode()
|
||||
|
||||
//~~~~~~功能:轮次的Ma-Form~~~~~~
|
||||
const {
|
||||
maFormModalRef,
|
||||
title,
|
||||
roundColumn,
|
||||
roundOption,
|
||||
handleRoundAddClick,
|
||||
handleRoundEditClick,
|
||||
handleRoundDelClick,
|
||||
handleRoundSubmit
|
||||
} = useRoundMaForm(projectId, handleSoDutExistsForceModal)
|
||||
const { maFormModalRef, title, roundColumn, roundOption, handleRoundAddClick, handleRoundEditClick, handleRoundDelClick, handleRoundSubmit } = useRoundMaForm(
|
||||
projectId,
|
||||
handleSoDutExistsForceModal
|
||||
)
|
||||
|
||||
// 大功能:~~~~~~右键菜单实现~~~~~~
|
||||
const {
|
||||
@@ -403,22 +334,12 @@ const {
|
||||
handleProblemShowClick,
|
||||
handleDoptionClickGreateCases,
|
||||
handleDoptionClickCopyDemand,
|
||||
handleCopyDemand
|
||||
handleCopyDemand,
|
||||
handleCreateRenji
|
||||
} = useRightClick(projectId, routeViewRef)
|
||||
|
||||
// ~~~~~~~~大功能:拖拽~~~~~~~~
|
||||
const {
|
||||
paoVisible,
|
||||
paoContainer,
|
||||
pao2Visible,
|
||||
pao2Container,
|
||||
ondrop,
|
||||
allowdrop,
|
||||
paoOk,
|
||||
paoCancel,
|
||||
paoOk2,
|
||||
paoCancel2
|
||||
} = useTreeDrag(projectId, routeViewRef)
|
||||
const { paoVisible, paoContainer, pao2Visible, pao2Container, ondrop, allowdrop, paoOk, paoCancel, paoOk2, paoCancel2 } = useTreeDrag(projectId, routeViewRef)
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -505,4 +426,12 @@ const {
|
||||
color: #f53f3f !important;
|
||||
}
|
||||
}
|
||||
.font-icon {
|
||||
font-size: 20px;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
.font-icon:hover {
|
||||
color: #f53f3fed;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -26,6 +26,12 @@
|
||||
</template>
|
||||
查看轮次下问题单
|
||||
</a-doption>
|
||||
<a-doption @click="handleRenjiTest">
|
||||
<template #icon>
|
||||
<icon-file />
|
||||
</template>
|
||||
点击生成人机交互界面测试
|
||||
</a-doption>
|
||||
</a-dgroup>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
@@ -47,7 +53,7 @@ const props = defineProps({
|
||||
}
|
||||
}
|
||||
})
|
||||
const emits = defineEmits(["update:visible", "click-problem-show"])
|
||||
const emits = defineEmits(["update:visible", "click-problem-show", "create-renji"])
|
||||
// 点击查看问题单列表
|
||||
const handleSelect = async (value) => {
|
||||
emits("update:visible") // 给父组件传递关闭弹窗
|
||||
@@ -60,6 +66,10 @@ const change = () => {
|
||||
const handleProblemShow = () => {
|
||||
emits("click-problem-show")
|
||||
}
|
||||
// 点击生成人机交互界面测试
|
||||
const handleRenjiTest = () => {
|
||||
emits("create-renji")
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
@@ -5,6 +5,7 @@ import caseApi from "@/api/project/case"
|
||||
import designApi from "@/api/project/designDemand"
|
||||
import demandApi from "@/api/project/testDemand"
|
||||
import { useTreeDataStore } from "@/store"
|
||||
|
||||
/**
|
||||
* 右键测试项节点与轮次的hook
|
||||
*/
|
||||
@@ -135,6 +136,19 @@ export function useRightClick(projectId, routeViewRef) {
|
||||
Message.error("复制失败,服务器错误")
|
||||
}
|
||||
}
|
||||
|
||||
// 新增点击增加人机交互界面测试
|
||||
const handleCreateRenji = async () => {
|
||||
const res = await designApi.create_rj({
|
||||
round_id: rightClickNode.nodekey,
|
||||
project_id: projectId.value
|
||||
})
|
||||
// 更新树状目录
|
||||
if (res.code === 200 && res.data) {
|
||||
treeDataStore.updateDesignDemandTreeData({ key: res.data + "-0" }, projectId.value)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
popupVisible,
|
||||
popupContainer,
|
||||
@@ -151,6 +165,7 @@ export function useRightClick(projectId, routeViewRef) {
|
||||
handleProblemShowClick,
|
||||
handleDoptionClickGreateCases,
|
||||
handleDoptionClickCopyDemand,
|
||||
handleCopyDemand
|
||||
handleCopyDemand,
|
||||
handleCreateRenji
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ const useTreeDataStore = defineStore("treeDataStore", {
|
||||
},
|
||||
// 新增删除designDemand后tree显示-注意传的是测试项的key
|
||||
async updateDesignDemandTreeData(data, projrctId) {
|
||||
console.log(data);
|
||||
let temp = data.key.split("-")
|
||||
temp.pop(-1)
|
||||
let roundKey = temp[0]
|
||||
|
||||
@@ -102,4 +102,14 @@ tool.htmlToTextWithDOM = (htmlString) => {
|
||||
return text
|
||||
}
|
||||
|
||||
// 判断数组是否包含'1'或者字符串是'1'
|
||||
tool.checkForCpuOrFPGA = (input_value) => {
|
||||
if (Array.isArray(input_value)) {
|
||||
return input_value.includes("1")
|
||||
} else if (typeof input_value === "string") {
|
||||
return input_value === "1"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export default tool
|
||||
|
||||
@@ -4,8 +4,11 @@ import MaInfo from "@/components/ma-info/index.vue"
|
||||
import useDutColumn from "@/views/project/round/hooks/useColumn"
|
||||
import useDesignColumn from "@/views/project/dut/hooks/useColumns"
|
||||
import useDemandColumn from "@/views/project/design-demand/hooks/useColumns"
|
||||
import tool from "@/utils/tool"
|
||||
import { useRoute } from "vue-router"
|
||||
|
||||
export default function useKeyToMaInfo() {
|
||||
const route = useRoute()
|
||||
// 初始状态为空
|
||||
let maInfoDom = ref(<Empty></Empty>)
|
||||
// 3个列信息
|
||||
@@ -17,6 +20,14 @@ export default function useKeyToMaInfo() {
|
||||
})
|
||||
const designColumns = useDesignColumn(undefined)
|
||||
const demandColumns = useDemandColumn(undefined)
|
||||
// 如果是FPGA则测试项ma-info去掉“测试项描述列”
|
||||
const demandColumnsNew = computed(() => {
|
||||
if (tool.checkForCpuOrFPGA(route.query.plant_type)) {
|
||||
return demandColumns.value
|
||||
}
|
||||
return demandColumns.value.filter((item) => item.dataIndex !== "testDesciption")
|
||||
})
|
||||
|
||||
// 函数:传入switch后的Promise以及是什么类型展示信息
|
||||
const fetchNodeDataAndSetMaInfo = async (resPromise: Promise<any>, nodeType: string) => {
|
||||
const res = await resPromise
|
||||
@@ -42,7 +53,7 @@ export default function useKeyToMaInfo() {
|
||||
maInfoDom.value = <MaInfo columns={designColumns.value} data={res.data} tableLayout="auto"></MaInfo>
|
||||
break
|
||||
case "demand":
|
||||
maInfoDom.value = <MaInfo columns={demandColumns.value} data={res.data} tableLayout="auto"></MaInfo>
|
||||
maInfoDom.value = <MaInfo columns={demandColumnsNew.value} data={res.data} tableLayout="auto"></MaInfo>
|
||||
break
|
||||
default:
|
||||
break
|
||||
|
||||
22
cdTMP/src/views/project/components/BatchCaseCreate/consts.ts
Normal file
22
cdTMP/src/views/project/components/BatchCaseCreate/consts.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { CaseUserType } from "./types"
|
||||
|
||||
export const initCaseData: CaseUserType = {
|
||||
parent_key: "",
|
||||
name: "",
|
||||
initialization: "软件正常启动,正常运行",
|
||||
premise: "软件正常启动,外部接口运行正常",
|
||||
summarize: "", // 用例综述
|
||||
test_step: "", // 综合字符串
|
||||
// FPGA
|
||||
sequence: "" // 时序图
|
||||
}
|
||||
|
||||
export const validationRules = {
|
||||
parent_key: [{ required: true, message: "必须选择归属需求" }],
|
||||
name: [{ required: true, message: "用例名称必填" }],
|
||||
initialization: [],
|
||||
premise: [],
|
||||
summarize: [],
|
||||
test_step: [{ required: true, message: "字符串录入信息必填" }],
|
||||
sequence: []
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import demandApi from "@/api/project/testDemand"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
import { computed, ref } from "vue"
|
||||
import { useRoute } from "vue-router"
|
||||
|
||||
interface OriginHouType {
|
||||
label: string
|
||||
value: number
|
||||
children: {
|
||||
label: string
|
||||
value: number
|
||||
key: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const route = useRoute()
|
||||
const casecadeLoading = ref(false)
|
||||
const originOptions = ref<OriginHouType[]>([])
|
||||
const casecadeOptions = computed(() => {
|
||||
return originOptions.value.map(({ children, ...rest }) => ({
|
||||
children: children.map(({ label, key }) => ({
|
||||
isLeaf: true,
|
||||
label,
|
||||
value: key
|
||||
})),
|
||||
...rest
|
||||
}))
|
||||
})
|
||||
// 初始化时候直接获取数据
|
||||
const fetchOptionsData = async () => {
|
||||
casecadeLoading.value = true
|
||||
try {
|
||||
// 获取轮次key
|
||||
const rawKey = Array.isArray(route.query.key) ? route.query.key[0] : route.query.key
|
||||
const round_key = rawKey?.split("-")[0]
|
||||
const res = await demandApi.getRelatedTestDemand({
|
||||
id: route.query.id,
|
||||
round: round_key
|
||||
})
|
||||
originOptions.value = res.data
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
Message.error("级联数据获取失败,请重新打开此页面!")
|
||||
} finally {
|
||||
casecadeLoading.value = false
|
||||
}
|
||||
}
|
||||
return { fetchOptionsData, casecadeOptions }
|
||||
}
|
||||
212
cdTMP/src/views/project/components/BatchCaseCreate/index.vue
Normal file
212
cdTMP/src/views/project/components/BatchCaseCreate/index.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<a-modal fullscreen unmount-on-close v-model:visible="visible" title="批量新增测试用例" ok-text="批量新增" :on-before-ok="handleSubmit">
|
||||
<a-form ref="formRef" :model="formData" layout="vertical">
|
||||
<a-table
|
||||
:data="formData"
|
||||
:columns="filterCaseColumns"
|
||||
:pagination="false"
|
||||
:hoverable="false"
|
||||
:scroll="isFPGA ? { x: 3000, y: 600 } : { x: 2500, y: 600 }"
|
||||
:bordered="{ wrapper: true, cell: true }"
|
||||
:draggable="{ type: 'handle', width: 40 }"
|
||||
@change="handleDraggleChange"
|
||||
>
|
||||
<template #xuhao="{ rowIndex }">
|
||||
{{ rowIndex + 1 }}
|
||||
</template>
|
||||
<template #parent_key="{ rowIndex }">
|
||||
<a-form-item help="归属当前轮次的测试项" :field="`${rowIndex}.parent_key`" :rules="validationRules.parent_key" hide-label>
|
||||
<a-cascader
|
||||
:style="{ width: '80%' }"
|
||||
allow-search
|
||||
allow-clear
|
||||
:options="casecadeOptions"
|
||||
placeholder="请选择归属的测试项"
|
||||
v-model="formData[rowIndex].parent_key"
|
||||
/>
|
||||
<a-button type="primary" :style="{ width: '20%' }" :disabled="rowIndex < 1" @click="handleTongshang($event, rowIndex, 'parent_key')">
|
||||
同上
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #name="{ rowIndex }">
|
||||
<a-form-item help="填写用例名称" :field="`${rowIndex}.name`" :rules="validationRules.name" hide-label>
|
||||
<a-input allow-clear v-model="formData[rowIndex].name"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #initialization="{ rowIndex }">
|
||||
<a-form-item help="请填写用例初始化" :field="`${rowIndex}.initialization`" hide-label>
|
||||
<a-textarea allow-clear auto-size v-model="formData[rowIndex].initialization"></a-textarea>
|
||||
<a-button
|
||||
type="primary"
|
||||
:style="{ width: '20%' }"
|
||||
:disabled="rowIndex < 1"
|
||||
@click="handleTongshang($event, rowIndex, 'initialization')"
|
||||
>
|
||||
同上
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #premise="{ rowIndex }">
|
||||
<a-form-item help="用例的前提与约束" :field="`${rowIndex}.premise`" hide-label>
|
||||
<a-textarea allow-clear auto-size v-model="formData[rowIndex].premise"></a-textarea>
|
||||
<a-button type="primary" :style="{ width: '20%' }" :disabled="rowIndex < 1" @click="handleTongshang($event, rowIndex, 'premise')">
|
||||
同上
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #summarize="{ rowIndex }">
|
||||
<a-form-item help="填写用例综述" :field="`${rowIndex}.summarize`" hide-label>
|
||||
<a-textarea allow-clear auto-size v-model="formData[rowIndex].summarize"></a-textarea>
|
||||
<a-button type="primary" :style="{ width: '20%' }" :disabled="rowIndex < 1" @click="handleTongshang($event, rowIndex, 'summarize')">
|
||||
同上
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #sequence="{ rowIndex }">
|
||||
<a-form-item :field="`${rowIndex}.sequence`" hide-label>
|
||||
<ma-editor v-model="formData[rowIndex].sequence" style="width: 100%" :id="'sequence' + rowIndex"></ma-editor>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #test_step="{ rowIndex }">
|
||||
<a-form-item :field="`${rowIndex}.test_step`" hide-label :rules="validationRules.test_step">
|
||||
<a-textarea allow-clear auto-size v-model="formData[rowIndex].test_step"></a-textarea>
|
||||
<PopupYonghu type="case" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #caozuo="{ rowIndex }">
|
||||
<a-space>
|
||||
<a-button type="text" @click="handlePlusIcon">
|
||||
<template #icon>
|
||||
<span class="icon"><icon-plus-circle /></span>
|
||||
</template>
|
||||
</a-button>
|
||||
<template v-if="!isLastOne">
|
||||
<a-button type="text" status="danger" @click="handleDelete(rowIndex)">
|
||||
<template #icon>
|
||||
<span class="icon"><icon-delete /></span>
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref } from "vue"
|
||||
import { initCaseData, validationRules } from "./consts"
|
||||
import { CasesSubmitType, CaseUserType } from "./types"
|
||||
import { Message, type FormInstance, type TableColumnData } from "@arco-design/web-vue"
|
||||
import { useRoute } from "vue-router"
|
||||
import { useTreeDataStore } from "@/store"
|
||||
import tool from "@/utils/tool"
|
||||
import useColumn from "./useColumn"
|
||||
import useCasecadeOptions from "./hooks/useCasecadeOptions"
|
||||
import PopupYonghu from "../BatchDemandCreate/components/YongHuTs"
|
||||
import caseApi from "@/api/project/case"
|
||||
|
||||
const visible = ref(false)
|
||||
const route = useRoute()
|
||||
const treeDataStore = useTreeDataStore()
|
||||
const isFPGA = ref(tool.checkForCpuOrFPGA(route.query.plant_type))
|
||||
const emit = defineEmits(["batchCaseCreateComplete"])
|
||||
|
||||
// columns
|
||||
const { caseColumns } = useColumn()
|
||||
const filterCaseColumns = computed(() => {
|
||||
return caseColumns.value.filter((col: TableColumnData) => {
|
||||
return !(isFPGA.value === false && col.dataIndex === "sequence")
|
||||
})
|
||||
})
|
||||
|
||||
// 拖拽处理
|
||||
const handleDraggleChange = (_data: any) => {
|
||||
formData.value = _data // 拖拽处理
|
||||
}
|
||||
|
||||
// 表单和表格数据
|
||||
const formRef = ref<FormInstance>()
|
||||
const formData = ref<CaseUserType[]>([{ ...initCaseData }])
|
||||
const isLastOne = computed(() => formData.value.length <= 1)
|
||||
|
||||
// hooks-提前获取信息
|
||||
const { fetchOptionsData, casecadeOptions } = useCasecadeOptions() // 获取当前轮次归属测试项
|
||||
const open = async () => {
|
||||
visible.value = true
|
||||
await nextTick()
|
||||
fetchOptionsData()
|
||||
}
|
||||
|
||||
// 处理“同上”按钮
|
||||
const handleTongshang = (_: MouseEvent, index: number, key: string) => {
|
||||
if (Array.isArray(formData.value[index - 1][key])) {
|
||||
formData.value[index][key] = [...formData.value[index - 1][key]]
|
||||
return
|
||||
}
|
||||
formData.value[index][key] = formData.value[index - 1][key]
|
||||
}
|
||||
|
||||
// 按钮函数
|
||||
const handlePlusIcon = () => {
|
||||
formData.value.push({ ...initCaseData })
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
formData.value.splice(index, 1)
|
||||
}
|
||||
const handleDeleteAll = () => {
|
||||
formData.value = [{ ...initCaseData }]
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
// 弹窗提交表单
|
||||
const handleSubmit = async () => {
|
||||
const validation = await formRef.value?.validate()
|
||||
if (validation === undefined) {
|
||||
// 验证通过-组装POST
|
||||
const submitData: CasesSubmitType = {
|
||||
project_id: route.query.id as unknown as number,
|
||||
cases: formData.value
|
||||
}
|
||||
try {
|
||||
const res = await caseApi.batchSave({ ...submitData })
|
||||
if (res.code === 60000) {
|
||||
// 标识录入成功了
|
||||
handleDeleteAll()
|
||||
res.data.forEach((it: any) => {
|
||||
treeDataStore.updateTestDemandTreeData({ key: it }, route.query.id)
|
||||
})
|
||||
emit("batchCaseCreateComplete")
|
||||
Message.success("批量新增用例成功!")
|
||||
return true
|
||||
} else {
|
||||
if (res.code === 60001) {
|
||||
// 有字符但没有@情况
|
||||
Message.error(res.message)
|
||||
} else if (res.code === 60002) {
|
||||
// 操作输入为空,即@前面为空
|
||||
Message.error(res.message)
|
||||
} else if (res.code === 60003) {
|
||||
// 预期为空,即@后面为空
|
||||
Message.error(res.message)
|
||||
} else {
|
||||
Message.error("测试步骤快捷字符串录入错误,请检查!")
|
||||
}
|
||||
}
|
||||
return false
|
||||
} catch (e) {
|
||||
console.log("录入用例时后前端报错,报错信息如下:", e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
||||
15
cdTMP/src/views/project/components/BatchCaseCreate/types.ts
Normal file
15
cdTMP/src/views/project/components/BatchCaseCreate/types.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export interface CaseUserType {
|
||||
parent_key: string // 关键:给后端一个测试项的key
|
||||
name: string
|
||||
initialization?: string // 软件正常启动,正常运行
|
||||
premise?: string // 软件正常启动,外部接口运行正常
|
||||
summarize?: string // 综述,取测试项?
|
||||
test_step: string // 给后端传字符串,后端解析
|
||||
// 后面是时序图
|
||||
sequence?: string // 时序图Base64-FPGA
|
||||
}
|
||||
|
||||
export interface CasesSubmitType {
|
||||
project_id: number
|
||||
cases: CaseUserType[]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { TableColumnData } from "@arco-design/web-vue"
|
||||
|
||||
import { ref } from "vue"
|
||||
|
||||
export default function () {
|
||||
const caseColumns = ref<TableColumnData[]>([
|
||||
{
|
||||
title: "序号",
|
||||
align: "center",
|
||||
dataIndex: "xuhao",
|
||||
slotName: "xuhao",
|
||||
width: 50
|
||||
},
|
||||
{
|
||||
title: "所属测试项(必填)",
|
||||
dataIndex: "parent_key",
|
||||
align: "center",
|
||||
slotName: "parent_key",
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: "名称(必填)",
|
||||
dataIndex: "name",
|
||||
align: "center",
|
||||
slotName: "name",
|
||||
width: 250
|
||||
},
|
||||
{
|
||||
title: "用例初始化",
|
||||
dataIndex: "initialization",
|
||||
align: "center",
|
||||
slotName: "initialization",
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: "前提和约束",
|
||||
dataIndex: "premise",
|
||||
align: "center",
|
||||
slotName: "premise",
|
||||
width: 350
|
||||
},
|
||||
{
|
||||
title: "用例综述",
|
||||
dataIndex: "summarize",
|
||||
align: "center",
|
||||
slotName: "summarize",
|
||||
width: 450
|
||||
},
|
||||
{
|
||||
title: "测试步骤-快捷步骤录入字符串(必填)",
|
||||
dataIndex: "test_step",
|
||||
align: "center",
|
||||
slotName: "test_step"
|
||||
},
|
||||
{
|
||||
title: "时序图",
|
||||
dataIndex: "sequence",
|
||||
align: "center",
|
||||
slotName: "sequence",
|
||||
width: 500
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
dataIndex: "caozuo",
|
||||
align: "center",
|
||||
slotName: "caozuo",
|
||||
width: 250,
|
||||
fixed: "right"
|
||||
}
|
||||
])
|
||||
|
||||
return { caseColumns }
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import styles from "./styles.module.less"
|
||||
|
||||
const PopupYonghu = (props: { type: "case" | "demand" }) => {
|
||||
const content =
|
||||
props.type === "demand" ? (
|
||||
<a-space direction="vertical">
|
||||
<p>使用“^”和“@”来完成:</p>
|
||||
<p>
|
||||
1. <a-tag color="red">“^”</a-tag>标注一个测试子项的开始
|
||||
</p>
|
||||
<p>
|
||||
2. <a-tag color="red">“@”</a-tag>标识连接
|
||||
</p>
|
||||
<p>
|
||||
3. 在测试子项名称紧跟着<a-tag color="red">“@”</a-tag>后面表示CPU项目的测试子项描述
|
||||
</p>
|
||||
<p>
|
||||
4. 在测试子项步骤紧跟着<a-tag color="red">“@”</a-tag>后面表示测试子项步骤的预期
|
||||
</p>
|
||||
<p>例示:</p>
|
||||
<p>
|
||||
<a-tag color="red">^</a-tag>测试子项名称<a-tag color="red">@</a-tag>测试子项描述(CPU项目)
|
||||
</p>
|
||||
<p>
|
||||
换行表示步骤输入<a-tag color="red">@</a-tag>我是该步骤的预期
|
||||
</p>
|
||||
<p>例子样子:</p>
|
||||
<p>^测试子项1号@测试子项1号描述</p>
|
||||
<p>步骤1号@预期1号</p>
|
||||
<p>步骤2号@预期2号</p>
|
||||
<p>步骤3号@预期3号</p>
|
||||
<p>^测试子项2号@测试子项2号描述</p>
|
||||
<p>步骤1号@预期1号</p>
|
||||
</a-space>
|
||||
) : (
|
||||
<a-space direction="vertical">
|
||||
<p>使用”@”:来完成步骤录入:</p>
|
||||
<p>1. 每一行代表一个用例步骤</p>
|
||||
<p>
|
||||
2. <a-tag color="red">@</a-tag>用于连接测试用例步骤和预期结果
|
||||
</p>
|
||||
<p>例示:</p>
|
||||
<p>步骤1操作输入@步骤1的预期结果</p>
|
||||
<p>步骤2操作输入@步骤2的预期结果</p>
|
||||
<p>步骤3操作输入@步骤3的预期结果</p>
|
||||
<p>步骤4操作输入@步骤4的预期结果</p>
|
||||
<p>步骤5操作输入@步骤5的预期结果</p>
|
||||
</a-space>
|
||||
)
|
||||
return (
|
||||
<a-popover title="录入规则" position="left" key={props.type}>
|
||||
{{
|
||||
default: () => <icon-question-circle-fill class={styles.suffix} />,
|
||||
content: () => content
|
||||
}}
|
||||
</a-popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default PopupYonghu
|
||||
@@ -0,0 +1,12 @@
|
||||
.suffix {
|
||||
margin-left: 10px;
|
||||
color: #86909c;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
transition: all 0.3s;
|
||||
color: rgb(237, 192, 68);
|
||||
}
|
||||
|
||||
.suffix:hover {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { demandOneType } from "./type"
|
||||
|
||||
// 初始化数据
|
||||
export const lineInitialData: demandOneType = {
|
||||
parent_key: "",
|
||||
ident: "",
|
||||
name: "",
|
||||
priority: "1",
|
||||
adequacy: "测试用例覆盖测试子项要求的全部内容。\a所有用例执行完毕,对于未执行的用例说明未执行原因。",
|
||||
testContent: "",
|
||||
testMethod: ["4"],
|
||||
testType: "4",
|
||||
testDesciption: ""
|
||||
}
|
||||
|
||||
export const validationRules = {
|
||||
parent_key: [{ required: true, message: "必须选择归属需求" }],
|
||||
ident: [{ required: true, message: "标识不能为空" }],
|
||||
name: [{ required: true, message: "名称不能为空" }],
|
||||
priority: [],
|
||||
adequacy: [{ required: true, message: "充分性不能为空" }],
|
||||
testMethod: [],
|
||||
testType: [{ required: true, message: "请选择测试类型" }],
|
||||
testDesciption: [],
|
||||
testContent: [{ required: true, message: "测试子项不能为空" }]
|
||||
}
|
||||
|
||||
export const priorityOptions = [
|
||||
{ label: "高", value: "1" },
|
||||
{ label: "中", value: "2" },
|
||||
{ label: "低", value: "3" }
|
||||
]
|
||||
@@ -0,0 +1,51 @@
|
||||
import designApi from "@/api/project/designDemand"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
import { computed, ref } from "vue"
|
||||
import { useRoute } from "vue-router"
|
||||
|
||||
// 定义后端返回的级联数据,注意修改后端也要修改此处
|
||||
interface OriginOptionsType {
|
||||
label: string
|
||||
value: number
|
||||
children: {
|
||||
label: string
|
||||
value: number
|
||||
children: {
|
||||
label: string
|
||||
value: number
|
||||
key: string // 这里要放options的value里面
|
||||
}[]
|
||||
}[]
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const route = useRoute()
|
||||
const casecadeLoading = ref(false)
|
||||
const originOptions = ref<OriginOptionsType[]>([])
|
||||
const casecadeOptions = computed(() => {
|
||||
return originOptions.value.map(({ children, ...rest }) => ({
|
||||
children: children.map(({ children, ...rest2 }) => ({
|
||||
children: children.map(({ label, key }) => ({
|
||||
label,
|
||||
value: key,
|
||||
isLeaf: true
|
||||
})),
|
||||
...rest2
|
||||
})),
|
||||
...rest
|
||||
}))
|
||||
})
|
||||
// 初始化是直接获取数据
|
||||
const fetchOptionsData = async () => {
|
||||
casecadeLoading.value = true
|
||||
try {
|
||||
const res = await designApi.getRelatedCasDesign({ id: route.query.id })
|
||||
originOptions.value = res.data
|
||||
} catch (e) {
|
||||
Message.error("级联数据获取失败,请重新打开此页面!")
|
||||
} finally {
|
||||
casecadeLoading.value = false
|
||||
}
|
||||
}
|
||||
return { fetchOptionsData, casecadeOptions }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { computed, ref } from "vue"
|
||||
import dictApi from "@/api/system/dict"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
|
||||
export default function () {
|
||||
const loading = ref(false)
|
||||
const originOption = ref([])
|
||||
const testMethodOptions = computed(() => {
|
||||
return originOption.value.map(({ title, key }) => ({
|
||||
label: title,
|
||||
value: key
|
||||
}))
|
||||
})
|
||||
// 请求字典中测试手段数据
|
||||
const fetchDictData = async () => {
|
||||
try {
|
||||
const res = await dictApi.getDictByCode({ code: "testMethod" })
|
||||
originOption.value = res.data
|
||||
loading.value = true
|
||||
} catch (e) {
|
||||
Message.error("获取测试手段选项失败,请关闭后重新打开!")
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { fetchDictData, loading, testMethodOptions }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { computed, ref } from "vue"
|
||||
import dictApi from "@/api/system/dict"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
|
||||
export default function () {
|
||||
const loading = ref(false)
|
||||
const originOption = ref([])
|
||||
const testTypeOptions = computed(() => {
|
||||
return originOption.value.map(({ title, key }) => ({
|
||||
label: title,
|
||||
value: key
|
||||
}))
|
||||
})
|
||||
// 请求字典中测试手段数据
|
||||
const fetchTestTypeDictData = async () => {
|
||||
try {
|
||||
const res = await dictApi.getDictByCode({ code: "testType" })
|
||||
originOption.value = res.data
|
||||
loading.value = true
|
||||
} catch (e) {
|
||||
Message.error("获取测试手段选项失败,请关闭后重新打开!")
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
return { fetchTestTypeDictData, loading, testTypeOptions }
|
||||
}
|
||||
222
cdTMP/src/views/project/components/BatchDemandCreate/index.vue
Normal file
222
cdTMP/src/views/project/components/BatchDemandCreate/index.vue
Normal file
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<a-modal fullscreen unmount-on-close v-model:visible="visible" title="批量新增测试项" ok-text="批量新增" :on-before-ok="handleSubmit">
|
||||
<a-form ref="formRef" :model="formData" layout="vertical">
|
||||
<a-table
|
||||
:data="formData"
|
||||
:columns="filterDemandColumns"
|
||||
:pagination="false"
|
||||
:hoverable="false"
|
||||
:scroll="{ x: 2500, y: 600 }"
|
||||
:bordered="{ wrapper: true, cell: true }"
|
||||
:draggable="{ type: 'handle', width: 40 }"
|
||||
@change="handleDraggleChange"
|
||||
>
|
||||
<template #xuhao="{ rowIndex }">
|
||||
{{ rowIndex + 1 }}
|
||||
</template>
|
||||
<template #parent_key="{ rowIndex }">
|
||||
<a-form-item help="测试所归属的需求" :field="`${rowIndex}.parent_key`" :rules="validationRules.parent_key" hide-label>
|
||||
<a-cascader
|
||||
:style="{ width: '80%' }"
|
||||
allow-search
|
||||
allow-clear
|
||||
:options="casecadeOptions"
|
||||
placeholder="请选择归属的设计需求"
|
||||
v-model="formData[rowIndex].parent_key"
|
||||
/>
|
||||
<a-button type="primary" :style="{ width: '20%' }" :disabled="rowIndex < 1" @click="handleTongshang($event, rowIndex, 'parent_key')">
|
||||
同上
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #ident="{ rowIndex }">
|
||||
<a-form-item help="测试项标识" :field="`${rowIndex}.ident`" :rules="validationRules.ident" hide-label>
|
||||
<a-input allow-clear v-model="formData[rowIndex].ident"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #name="{ rowIndex }">
|
||||
<a-form-item help="测试项名称" :field="`${rowIndex}.name`" :rules="validationRules.name" hide-label>
|
||||
<a-input allow-clear v-model="formData[rowIndex].name"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #priority="{ rowIndex }">
|
||||
<a-form-item help="选择优先级" :field="`${rowIndex}.priority`" :rules="validationRules.priority" hide-label>
|
||||
<a-select :options="priorityOptions" v-model="formData[rowIndex].priority"></a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #adequacy="{ rowIndex }">
|
||||
<a-form-item help="充分性描述,\a表示word换行" :field="`${rowIndex}.adequacy`" :rules="validationRules.adequacy" hide-label>
|
||||
<a-textarea allow-clear auto-size v-model="formData[rowIndex].adequacy"></a-textarea>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #testMethod="{ rowIndex }">
|
||||
<a-form-item help="选择测试手段" :field="`${rowIndex}.testMethod`" :rules="validationRules.testMethod" hide-label>
|
||||
<a-select multiple :options="testMethodOptions" v-model="formData[rowIndex].testMethod"></a-select>
|
||||
<a-button type="primary" :style="{ width: '20%' }" :disabled="rowIndex < 1" @click="handleTongshang($event, rowIndex, 'testMethod')">
|
||||
同上
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #testType="{ rowIndex }">
|
||||
<a-form-item help="选择测试类型" :field="`${rowIndex}.testType`" :rules="validationRules.testType" hide-label>
|
||||
<a-select allow-search :options="testTypeOptions" v-model="formData[rowIndex].testType"></a-select>
|
||||
<a-button type="primary" :style="{ width: '20%' }" :disabled="rowIndex < 1" @click="handleTongshang($event, rowIndex, 'testType')">
|
||||
同上
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #testDesciption="{ rowIndex }">
|
||||
<a-form-item help="填写测试项描述" :field="`${rowIndex}.testDesciption`" :rules="validationRules.testDesciption" hide-label>
|
||||
<a-textarea allow-clear auto-size v-model="formData[rowIndex].testDesciption"></a-textarea>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #testContent="{ rowIndex }">
|
||||
<a-form-item :field="`${rowIndex}.testContent`" hide-label :rules="validationRules.testContent">
|
||||
<a-textarea allow-clear auto-size v-model="formData[rowIndex].testContent"></a-textarea>
|
||||
<YonghuTs type="demand" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template #caozuo="{ rowIndex }">
|
||||
<a-space>
|
||||
<a-button type="text" @click="handlePlusIcon">
|
||||
<template #icon>
|
||||
<span class="icon"><icon-plus-circle /></span>
|
||||
</template>
|
||||
</a-button>
|
||||
<template v-if="!isLastOne">
|
||||
<a-button type="text" status="danger" @click="handleDelete(rowIndex)">
|
||||
<template #icon>
|
||||
<span class="icon"><icon-delete /></span>
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref } from "vue"
|
||||
import type { demandBatchType, demandOneType } from "./type"
|
||||
import { Message, type FormInstance, type TableColumnData } from "@arco-design/web-vue"
|
||||
import { lineInitialData, validationRules, priorityOptions } from "./consts"
|
||||
import { useRoute } from "vue-router"
|
||||
import { useTreeDataStore } from "@/store"
|
||||
import useColumns from "./useColumn"
|
||||
import useCasecadeOptions from "./hooks/useCasecadeOptions"
|
||||
import useTestMethodOptions from "./hooks/useTestMethodOptions"
|
||||
import useTestTypeOptions from "./hooks/useTestTypeOptions"
|
||||
import tool from "@/utils/tool"
|
||||
import YonghuTs from "./components/YongHuTs"
|
||||
import demandApi from "@/api/project/testDemand"
|
||||
|
||||
// outInit
|
||||
const route = useRoute()
|
||||
const treeDataStore = useTreeDataStore()
|
||||
const isFPGA = ref(tool.checkForCpuOrFPGA(route.query.plant_type))
|
||||
|
||||
const emit = defineEmits(["batchDemandCreateComplete"])
|
||||
|
||||
// 表格数据-动态绑定
|
||||
const formRef = ref<FormInstance>()
|
||||
const formData = ref<demandOneType[]>([{ ...lineInitialData }])
|
||||
const isLastOne = computed(() => formData.value.length <= 1)
|
||||
|
||||
// columns-根据isFPGA计算属性
|
||||
const { demandColumns } = useColumns()
|
||||
const filterDemandColumns = computed(() => {
|
||||
return demandColumns.value.filter((col: TableColumnData) => {
|
||||
return !(isFPGA.value === false && col.dataIndex === "testDesciption")
|
||||
})
|
||||
})
|
||||
|
||||
// 初始化级联下拉框数据
|
||||
const { fetchOptionsData, casecadeOptions } = useCasecadeOptions()
|
||||
// 初始化测试手段数据
|
||||
const { fetchDictData, testMethodOptions } = useTestMethodOptions()
|
||||
// 初始化测试类型数据
|
||||
const { fetchTestTypeDictData, testTypeOptions } = useTestTypeOptions()
|
||||
|
||||
const visible = ref(false)
|
||||
const open = async () => {
|
||||
visible.value = true
|
||||
await nextTick()
|
||||
fetchOptionsData() // 打开时获取级联下拉框数据
|
||||
fetchDictData() // 打开时获取测试手段数据
|
||||
fetchTestTypeDictData() // 打开时获取测试类型数据
|
||||
}
|
||||
|
||||
// 操作行
|
||||
const handlePlusIcon = () => {
|
||||
formData.value.push({ ...lineInitialData })
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
formData.value.splice(index, 1)
|
||||
}
|
||||
const handleDeleteAll = () => {
|
||||
formData.value = [{ ...lineInitialData }]
|
||||
}
|
||||
const handleTongshang = (_: MouseEvent, index: number, key: string) => {
|
||||
if (Array.isArray(formData.value[index - 1][key])) {
|
||||
formData.value[index][key] = [...formData.value[index - 1][key]]
|
||||
return
|
||||
}
|
||||
formData.value[index][key] = formData.value[index - 1][key]
|
||||
}
|
||||
const handleDraggleChange = (_data: any) => {
|
||||
formData.value = _data // 拖拽处理
|
||||
}
|
||||
|
||||
// 异步提交按钮
|
||||
const handleSubmit = async () => {
|
||||
// 首先Form校验
|
||||
const validation = await formRef.value?.validate()
|
||||
if (validation === undefined) {
|
||||
const submitData: demandBatchType = {
|
||||
project_id: route.query.id as unknown as number,
|
||||
demands: formData.value
|
||||
}
|
||||
try {
|
||||
const res = await demandApi.batchSave({ ...submitData })
|
||||
// 单独接口:如果res.data==='ok',则创建成功
|
||||
if (res.code === 200991) {
|
||||
handleDeleteAll()
|
||||
res.data.forEach((it: any) => {
|
||||
treeDataStore.updateDesignDemandTreeData({ key: it }, route.query.id)
|
||||
})
|
||||
emit("batchDemandCreateComplete")
|
||||
Message.success("批量新增成功!")
|
||||
return true
|
||||
} else {
|
||||
if (res.code === 500102) {
|
||||
// 有+但没有测试项名称-后面再处理
|
||||
Message.error(res.message)
|
||||
} else if (res.code === 500103) {
|
||||
// 有+的行解析错误
|
||||
Message.error(res.message)
|
||||
} else if (res.code === 500104) {
|
||||
// 有-但无+的行解析错误
|
||||
Message.error(res.message)
|
||||
} else {
|
||||
Message.error("测试子项字符串解析错误,请检查后重试!")
|
||||
}
|
||||
return false
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("录入事后前端报错,报错信息如下:", e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
||||
16
cdTMP/src/views/project/components/BatchDemandCreate/type.ts
Normal file
16
cdTMP/src/views/project/components/BatchDemandCreate/type.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface demandOneType {
|
||||
parent_key: string // 设计需求的key
|
||||
ident: string
|
||||
name: string
|
||||
priority: string
|
||||
adequacy: string
|
||||
testMethod?: string[] | string
|
||||
testType: string
|
||||
testDesciption?: string
|
||||
testContent: string
|
||||
}
|
||||
|
||||
export interface demandBatchType {
|
||||
project_id: number
|
||||
demands: demandOneType[]
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { TableColumnData } from "@arco-design/web-vue"
|
||||
|
||||
import { ref } from "vue"
|
||||
|
||||
export default function () {
|
||||
const demandColumns = ref<TableColumnData[]>([
|
||||
{
|
||||
title: "序号",
|
||||
align: "center",
|
||||
dataIndex: "xuhao",
|
||||
slotName: "xuhao",
|
||||
width: 50
|
||||
},
|
||||
{
|
||||
title: "设计需求",
|
||||
dataIndex: "parent_key",
|
||||
align: "center",
|
||||
slotName: "parent_key",
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: "标识(必填)",
|
||||
dataIndex: "ident",
|
||||
align: "center",
|
||||
slotName: "ident",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "名称(必填)",
|
||||
dataIndex: "name",
|
||||
align: "center",
|
||||
slotName: "name",
|
||||
width: 250
|
||||
},
|
||||
{
|
||||
title: "优先级",
|
||||
dataIndex: "priority",
|
||||
align: "center",
|
||||
slotName: "priority",
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: "充分性描述(必填)",
|
||||
dataIndex: "adequacy",
|
||||
align: "center",
|
||||
slotName: "adequacy",
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: "测试手段",
|
||||
dataIndex: "testMethod",
|
||||
align: "center",
|
||||
slotName: "testMethod",
|
||||
width: 210
|
||||
},
|
||||
{
|
||||
title: "测试类型",
|
||||
dataIndex: "testType",
|
||||
align: "center",
|
||||
slotName: "testType",
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: "测试项描述",
|
||||
dataIndex: "testDesciption",
|
||||
align: "center",
|
||||
slotName: "testDesciption",
|
||||
width: 350
|
||||
},
|
||||
{
|
||||
title: "测试子项(必填)",
|
||||
dataIndex: "testContent",
|
||||
align: "center",
|
||||
slotName: "testContent"
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
dataIndex: "caozuo",
|
||||
align: "center",
|
||||
slotName: "caozuo",
|
||||
width: 250,
|
||||
fixed: "right"
|
||||
}
|
||||
])
|
||||
|
||||
return { demandColumns }
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { TableColumnData } from "@arco-design/web-vue"
|
||||
|
||||
export const designColumn: TableColumnData[] = [
|
||||
{
|
||||
title: "序号",
|
||||
align: "center",
|
||||
dataIndex: "xuhao",
|
||||
slotName: "xuhao",
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: "标识",
|
||||
dataIndex: "ident",
|
||||
align: "center",
|
||||
slotName: "ident",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "名称(必填)",
|
||||
dataIndex: "name",
|
||||
align: "center",
|
||||
slotName: "name",
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: "章节号(必填)",
|
||||
dataIndex: "chapter",
|
||||
align: "center",
|
||||
slotName: "chapter",
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: "类型(必填)",
|
||||
dataIndex: "demandType",
|
||||
align: "center",
|
||||
slotName: "demandType",
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: "需求描述",
|
||||
dataIndex: "description",
|
||||
align: "center",
|
||||
slotName: "description",
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
fixed: "right", // 固定右侧
|
||||
dataIndex: "caozuo",
|
||||
align: "center",
|
||||
slotName: "caozuo",
|
||||
width: 100
|
||||
}
|
||||
]
|
||||
203
cdTMP/src/views/project/components/BatchDesignCreate/index.tsx
Normal file
203
cdTMP/src/views/project/components/BatchDesignCreate/index.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
import { computed, defineComponent, PropType, ref } from "vue"
|
||||
import type { BatchFormData } from "./types"
|
||||
import { designColumn } from "./columns"
|
||||
import commonApi from "@/api/common"
|
||||
import { FormInstance, Message } from "@arco-design/web-vue"
|
||||
import { useRoute } from "vue-router"
|
||||
import designApi from "@/api/project/designDemand"
|
||||
import { useTreeDataStore } from "@/store"
|
||||
|
||||
// props.type类型
|
||||
type CreateType = "design" | "demand" | "case"
|
||||
|
||||
// 常量
|
||||
const typeTitle = {
|
||||
design: "设计需求",
|
||||
demand: "测试项",
|
||||
case: "用例"
|
||||
}
|
||||
|
||||
const BatchCreate = defineComponent({
|
||||
name: "BatchCreate",
|
||||
emits: ["batchCreateFinish"],
|
||||
props: {
|
||||
type: {
|
||||
type: String as PropType<CreateType>,
|
||||
default: "design"
|
||||
}
|
||||
},
|
||||
setup({ type }, { expose, emit }) {
|
||||
const visible = ref(false)
|
||||
const route = useRoute()
|
||||
const treeDataStore = useTreeDataStore()
|
||||
|
||||
// 表单的引用
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
// 请求需求类型-预备数据
|
||||
const xqType = ref<any>([])
|
||||
;(function () {
|
||||
commonApi.getDict("demandType").then((res: any) => {
|
||||
xqType.value = res.data
|
||||
})
|
||||
})()
|
||||
const xqTypeOptions = computed(() => {
|
||||
return xqType.value.map(({ key, title }) => ({
|
||||
label: title,
|
||||
value: key
|
||||
}))
|
||||
})
|
||||
|
||||
// 初始化数据-默认有一行空数据
|
||||
const newData = {
|
||||
ident: "",
|
||||
name: "",
|
||||
chapter: "",
|
||||
demandType: "1",
|
||||
description: ""
|
||||
}
|
||||
|
||||
const formData = ref<BatchFormData[]>([{ ...newData }])
|
||||
|
||||
// 定义表单的验证规则
|
||||
const validationRules = {
|
||||
ident: [],
|
||||
name: [{ required: true, message: "需求名称不能为空" }],
|
||||
chapter: [{ required: true, message: "可为'/',不能为空" }],
|
||||
demandType: [{ required: true, message: "需求类型不能为空" }],
|
||||
description: []
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
// data数据操作
|
||||
const handleNewLine = () => {
|
||||
formData.value.push({ ...newData })
|
||||
}
|
||||
|
||||
const handleDeleteAll = () => {
|
||||
formData.value = [{ ...newData }]
|
||||
}
|
||||
|
||||
const handleDeletePoint = (idx: number) => {
|
||||
formData.value.splice(idx, 1)
|
||||
}
|
||||
|
||||
// 提交操作-可阻止关闭模态框
|
||||
const handleSubmit = async () => {
|
||||
// 配置提交变量
|
||||
const projectId = route.query.id
|
||||
const key = route.query.key
|
||||
// 修改符合后端要求的数据要求content和title
|
||||
const data = formData.value.map(({ name, description, ...rest }) => ({
|
||||
title: name,
|
||||
content: description,
|
||||
...rest
|
||||
}))
|
||||
const validation = await formRef.value?.validate()
|
||||
if (validation === undefined) {
|
||||
// 验证成功
|
||||
try {
|
||||
const res = await designApi.multiSave({ projectId, key, data })
|
||||
handleDeleteAll()
|
||||
treeDataStore.updateDesignDemandTreeData(res.data, projectId)
|
||||
emit("batchCreateFinish")
|
||||
Message.success("批量新增成功!")
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
expose({ open })
|
||||
|
||||
return () => (
|
||||
<div class="batch-create-container">
|
||||
<a-modal
|
||||
fullscreen
|
||||
unmount-on-close
|
||||
v-model:visible={visible.value}
|
||||
title={"批量新增" + typeTitle[type]}
|
||||
ok-text="批量新增"
|
||||
on-before-ok={handleSubmit}
|
||||
>
|
||||
<a-space>
|
||||
<a-button type="primary" style={{ marginBottom: "10px" }} onClick={handleNewLine}>
|
||||
{{
|
||||
icon: () => <icon-plus />,
|
||||
default: () => "新增一行"
|
||||
}}
|
||||
</a-button>
|
||||
<a-popconfirm content="您确定要重置?" onOk={handleDeleteAll}>
|
||||
<a-button type="primary" status="warning" style={{ marginBottom: "10px" }}>
|
||||
重置
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
<a-form ref={formRef} model={formData.value} layout="vertical">
|
||||
<a-table
|
||||
data={formData.value}
|
||||
columns={designColumn}
|
||||
pagination={false}
|
||||
hoverable={false}
|
||||
scroll={{ x: 1500 }}
|
||||
bordered={{ wrapper: true, cell: true }}
|
||||
v-slots={{
|
||||
xuhao: ({ rowIndex }) => rowIndex + 1,
|
||||
ident: ({ rowIndex }) => (
|
||||
<a-form-item help="例如SJGL" field={`${rowIndex}.ident`} rules={validationRules.ident} hide-label>
|
||||
<a-input allow-clear v-model={formData.value[rowIndex].ident} placeholder="无须写前缀" />
|
||||
</a-form-item>
|
||||
),
|
||||
name: ({ rowIndex }) => (
|
||||
<a-form-item help="章节名称" field={`${rowIndex}.name`} rules={validationRules.name} hide-label>
|
||||
<a-input allow-clear v-model={formData.value[rowIndex].name} placeholder="请填写需求名称" />
|
||||
</a-form-item>
|
||||
),
|
||||
chapter: ({ rowIndex }) => (
|
||||
<a-form-item help="可填写'/'为章节号" field={`${rowIndex}.chapter`} rules={validationRules.chapter} hide-label>
|
||||
<a-input allow-clear v-model={formData.value[rowIndex].chapter} placeholder="请填写章节号" />
|
||||
</a-form-item>
|
||||
),
|
||||
demandType: ({ rowIndex }) => (
|
||||
<a-form-item help="接口需补充内容" field={`${rowIndex}.demandType`} rules={validationRules.demandType} hide-label>
|
||||
<a-select v-model={formData.value[rowIndex].demandType} options={xqTypeOptions.value} />
|
||||
</a-form-item>
|
||||
),
|
||||
description: ({ rowIndex }) => (
|
||||
<a-form-item field={`${rowIndex}.description`} rules={validationRules.description} hide-label>
|
||||
<ma-editor v-model={formData.value[rowIndex].description} style="width: 100%" id={"description" + rowIndex}></ma-editor>
|
||||
</a-form-item>
|
||||
),
|
||||
caozuo: ({ rowIndex }) => (
|
||||
<div>
|
||||
<a-popover title="新增">
|
||||
<a-button type="text" onClick={handleNewLine}>
|
||||
{{
|
||||
icon: () => <icon-plus />
|
||||
}}
|
||||
</a-button>
|
||||
</a-popover>
|
||||
<a-popover title="删除">
|
||||
<a-button type="text" status="danger" onClick={() => handleDeletePoint(rowIndex)}>
|
||||
{{
|
||||
icon: () => <icon-delete />
|
||||
}}
|
||||
</a-button>
|
||||
</a-popover>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
></a-table>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default BatchCreate
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface BatchFormData {
|
||||
ident?: string
|
||||
name: string
|
||||
chapter?: string
|
||||
demandType: string
|
||||
description?: string
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="ai-modal-container">
|
||||
<a-modal v-model:visible="visible" width="80%" unmount-on-close draggable :footer="false">
|
||||
<a-modal v-model:visible="visible" width="90%" unmount-on-close draggable :footer="false">
|
||||
<template #title> AI生成测试项 </template>
|
||||
<div class="flex flex-col">
|
||||
<a-button type="primary" :disabled="generateLoading" @click="generateClick">{{
|
||||
@@ -44,10 +44,12 @@
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
<div class="m-2 flex justify-start items-center">
|
||||
<div class="label">测试项描述:</div>
|
||||
<div class="input flex-1">
|
||||
<a-input v-model="item.demandDescription"></a-input>
|
||||
</div>
|
||||
<template v-if="isFPGA">
|
||||
<div class="label">测试项描述:</div>
|
||||
<div class="input flex-1">
|
||||
<a-input v-model="item.demandDescription"></a-input>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="arco-table arco-table-size-large arco-table-border arco-table-stripe arco-table-hover">
|
||||
<div class="arco-table-container">
|
||||
@@ -59,11 +61,19 @@
|
||||
<span class="arco-table-th-title label">子项序号</span>
|
||||
</span>
|
||||
</th>
|
||||
<th class="arco-table-th" :width="400">
|
||||
<th class="arco-table-th" :width="200">
|
||||
<span class="arco-table-cell arco-table-cell-align-center">
|
||||
<span class="arco-table-th-title label">测试子项描述</span>
|
||||
<span class="arco-table-th-title label">子项名称</span>
|
||||
</span>
|
||||
</th>
|
||||
<template v-if="!isFPGA">
|
||||
<th class="arco-table-th" :width="250">
|
||||
<span class="arco-table-cell arco-table-cell-align-center">
|
||||
<span class="arco-table-th-title label">测试子项描述</span>
|
||||
</span>
|
||||
</th>
|
||||
</template>
|
||||
|
||||
<th class="arco-table-th" :width="800">
|
||||
<span class="arco-table-cell arco-table-cell-align-center">
|
||||
<span class="arco-table-th-title label">测试子项步骤</span>
|
||||
@@ -84,6 +94,17 @@
|
||||
<a-textarea auto-size placeholder="请填写测试子项名称" v-model="row.name"></a-textarea>
|
||||
</span>
|
||||
</td>
|
||||
<template v-if="!isFPGA">
|
||||
<td class="arco-table-td">
|
||||
<span class="arco-table-cell">
|
||||
<a-textarea
|
||||
auto-size
|
||||
placeholder="请填写测试子项描述"
|
||||
v-model="row.subDescription"
|
||||
></a-textarea>
|
||||
</span>
|
||||
</td>
|
||||
</template>
|
||||
<td class="arco-table-td">
|
||||
<span class="arco-table-cell">
|
||||
<OpeAndExpect v-model="row.subStep" />
|
||||
@@ -121,7 +142,9 @@ import demandApi from "@/api/project/testDemand"
|
||||
import ParentPreview from "@/views/project/ParentPreview/index.vue"
|
||||
|
||||
// 常量
|
||||
const route = useRoute()
|
||||
const indexTu = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳㉑㉒㉓㉔㉕㉖㉗㉘㉙㉚"
|
||||
const isFPGA = tool.checkForCpuOrFPGA(route.query.plant_type)
|
||||
|
||||
// 初始化测试类型-一起请求处理错误
|
||||
const testType = ref<any>([])
|
||||
@@ -138,7 +161,7 @@ const fetchTestType = async () => {
|
||||
fetchTestType()
|
||||
|
||||
// 初始化设计需求
|
||||
const route = useRoute()
|
||||
|
||||
const currentKey: string = route.query.key as string
|
||||
const getDesign = async () => {
|
||||
try {
|
||||
@@ -165,7 +188,6 @@ const generateClick = async () => {
|
||||
startProgressSimulation()
|
||||
// 变量:给AI的问题
|
||||
const question = tool.htmlToTextWithDOM(designObj.value?.description || "")
|
||||
console.log("给AI的问题如下:", question)
|
||||
// 请求后处理结果
|
||||
const res = await aiApi.getAiTestItem({ question: question, stream: false })
|
||||
// 判断真实接口和开发环境接口
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { ref } from "vue"
|
||||
import PinYinMatch from "pinyin-match"
|
||||
import tool from "@/utils/tool"
|
||||
import { useRoute } from "vue-router"
|
||||
|
||||
export default function (crudOrFormRef: any) {
|
||||
const route = useRoute()
|
||||
const isFpga = tool.checkForCpuOrFPGA(route.query.plant_type)
|
||||
const crudColumns = ref([
|
||||
{
|
||||
title: "ID",
|
||||
@@ -83,8 +87,7 @@ export default function (crudOrFormRef: any) {
|
||||
formType: "textarea",
|
||||
maxLength: 256,
|
||||
commonRules: [{ required: true, message: "充分性描述必填" }],
|
||||
addDefaultValue:
|
||||
"测试用例覆盖XX子项名称1、XX子项名称2、XX子项名称3子项要求的全部内容。\n所有用例执行完毕,对于未执行的用例说明未执行原因。"
|
||||
addDefaultValue: "测试用例覆盖XX子项名称1、XX子项名称2、XX子项名称3子项要求的全部内容。\n所有用例执行完毕,对于未执行的用例说明未执行原因。"
|
||||
},
|
||||
{
|
||||
title: "测试项描述",
|
||||
@@ -92,7 +95,10 @@ export default function (crudOrFormRef: any) {
|
||||
dataIndex: "testDesciption",
|
||||
formType: "textarea",
|
||||
maxLength: 256,
|
||||
placeholder: "请填写整体测试项的描述"
|
||||
placeholder: "FPGA填写-请填写整体测试项的描述",
|
||||
display: isFpga,
|
||||
addDisplay: isFpga,
|
||||
editDisplay: isFpga
|
||||
},
|
||||
{
|
||||
title: "测试子项",
|
||||
@@ -119,6 +125,15 @@ export default function (crudOrFormRef: any) {
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "测试项描述",
|
||||
dataIndex: "subDescription",
|
||||
placeholder: "非FPGA填写每个子项一句话描述",
|
||||
formType:"textarea",
|
||||
display: !isFpga,
|
||||
addDisplay: !isFpga,
|
||||
editDisplay: !isFpga
|
||||
},
|
||||
{
|
||||
title: "操作与预期",
|
||||
dataIndex: "subStep",
|
||||
|
||||
@@ -88,9 +88,11 @@ import useRalateDemand from "./hooks/useRalateDemand"
|
||||
import demandApi from "@/api/project/testDemand"
|
||||
import ReplaceModel from "@/views/project/opeSets/components/DesignTable/ReplaceModal.vue"
|
||||
import ReplacePriority from "@/views/project/opeSets/components/DemandTable/ReplacePriority.vue"
|
||||
|
||||
// inits
|
||||
const route = useRoute()
|
||||
const treeDataStore = useTreeDataStore()
|
||||
|
||||
// refs
|
||||
const crudRef = ref(null)
|
||||
|
||||
|
||||
@@ -44,12 +44,7 @@ const DesignSubForm = defineComponent({
|
||||
{{
|
||||
title: () => <span>[设计需求]-{title.value}</span>,
|
||||
default: () => (
|
||||
<ma-form
|
||||
ref={formRef}
|
||||
v-model={formData.value}
|
||||
options={options.value}
|
||||
columns={columnOptions.value}
|
||||
>
|
||||
<ma-form ref={formRef} v-model={formData.value} options={options.value} columns={columnOptions.value}>
|
||||
{{
|
||||
"inputPrepend-ident": () => <span>SJ-XX-</span>
|
||||
}}
|
||||
|
||||
@@ -3,12 +3,12 @@ import tool from "@/utils/tool"
|
||||
import useColumn from "../hooks/useColumns"
|
||||
|
||||
// 设置不同ma-form选项
|
||||
export default function useOptions(formRef: any) {
|
||||
export default function useOptions(formRef?: any) {
|
||||
const options = ref({
|
||||
showButtons: false,
|
||||
labelAlign: "center"
|
||||
})
|
||||
const crudColumns = useColumn(formRef)
|
||||
const crudColumns = useColumn()
|
||||
const columnOptions = computed(() => {
|
||||
return tool.renameKeyInArray(crudColumns.value, "commonRules", "rules")
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="uploadContainer">
|
||||
<span :style="{ marginBottom: '10px', flex: '0 1 150px' }">上传需求.docx:</span>
|
||||
<a-upload
|
||||
:style="{ marginBottom: '10px' }"
|
||||
:style="{ marginBottom: '10px', marginLeft: '9px' }"
|
||||
:limit="1"
|
||||
accept=".docx"
|
||||
:action="`/api/dut_upload/upload_xq_docx/?parseChapter=${parseChapter}`"
|
||||
@@ -24,16 +24,15 @@
|
||||
></a-upload>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-[350px]">要解析的章节名称:</span>
|
||||
<span class="w-87.5">要解析的章节名称:</span>
|
||||
<a-input placeholder="输入要解析的章节名称" v-model="parseChapter"></a-input>
|
||||
<span class="w-[350px]">选择需求录入类型:</span>
|
||||
<span class="w-87.5">选择需求录入类型:</span>
|
||||
<a-select allow-search v-model="selectValue">
|
||||
<a-option v-for="item in demandType" :key="item.key" :value="item.key">{{ item.title }}</a-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<a-alert :style="{ margin: '10px 0' }" type="warning">
|
||||
只能上传.docx,<span class="important-text">如果有visio图请替换为普通图片上传</span
|
||||
>,请在需求规格说明文档中操作 ->
|
||||
只能上传.docx,<span class="important-text">如果有visio图请替换为普通图片上传</span>,请在需求规格说明文档中操作 ->
|
||||
<span class="important-text">引用 -> 目录 -> 自定义目录 -> 显示级别改为6</span>以上保存后上传
|
||||
</a-alert>
|
||||
<div class="operation-container">
|
||||
@@ -45,60 +44,26 @@
|
||||
</div>
|
||||
<a-spin :loading="loading" tip="解析word完成,正在渲染界面..." :style="{ width: '100%' }">
|
||||
<div class="demand-container">
|
||||
<a-list
|
||||
@page-change="handlePageChange"
|
||||
:data="htmlData"
|
||||
:pagination-props="{ defaultPageSize: 15, total: htmlData.length }"
|
||||
>
|
||||
<a-list @page-change="handlePageChange" :data="htmlData" :pagination-props="{ defaultPageSize: 15, total: htmlData.length }">
|
||||
<template #item="{ item, index }">
|
||||
<a-list-item>
|
||||
<div class="item-container">
|
||||
<a-input-group>
|
||||
<a-input
|
||||
placeholder="章节号"
|
||||
v-model="item.chapter"
|
||||
:style="{ width: '100px' }"
|
||||
@click.stop.prevent
|
||||
></a-input>
|
||||
<a-input
|
||||
placeholder="标题"
|
||||
v-model="item.title"
|
||||
:style="{ width: '300px' }"
|
||||
@click.stop.prevent
|
||||
></a-input>
|
||||
<a-input
|
||||
placeholder="标识"
|
||||
v-model="item.ident"
|
||||
:style="{ width: '200px' }"
|
||||
@click.stop.prevent
|
||||
></a-input>
|
||||
<a-select
|
||||
:style="{ width: '150px' }"
|
||||
placeholder="请选择设计需求类型"
|
||||
@click.stop.prevent
|
||||
v-model="item.demandType"
|
||||
>
|
||||
<a-input placeholder="章节号" v-model="item.chapter" :style="{ width: '100px' }" @click.stop.prevent></a-input>
|
||||
<a-input placeholder="标题" v-model="item.title" :style="{ width: '300px' }" @click.stop.prevent></a-input>
|
||||
<a-input placeholder="标识" v-model="item.ident" :style="{ width: '200px' }" @click.stop.prevent></a-input>
|
||||
<a-select :style="{ width: '150px' }" placeholder="请选择设计需求类型" @click.stop.prevent v-model="item.demandType">
|
||||
<a-option v-for="it in demandType" :value="it.key">{{ it.title }}</a-option>
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
<a-button-group>
|
||||
<a-button
|
||||
type="primary"
|
||||
status="success"
|
||||
size="small"
|
||||
@click.stop.prevent="handledownCreate(index)"
|
||||
>
|
||||
<a-button type="primary" status="success" size="small" @click.stop.prevent="handledownCreate(index)">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
下方新增
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
status="danger"
|
||||
size="small"
|
||||
@click.stop.prevent="handleDelete(index)"
|
||||
>
|
||||
<a-button type="primary" status="danger" size="small" @click.stop.prevent="handleDelete(index)">
|
||||
<template #icon>
|
||||
<icon-delete />
|
||||
</template>
|
||||
@@ -146,8 +111,7 @@ const modalVisible = ref(false)
|
||||
const htmlData = ref([])
|
||||
|
||||
// ~~~~1.list~~~~
|
||||
const { loading, handleCreateAtLatest, handleResetData, handledownCreate, handlePageChange, handleDelete } =
|
||||
useListOperaton(htmlData)
|
||||
const { loading, handleCreateAtLatest, handleResetData, handledownCreate, handlePageChange, handleDelete } = useListOperaton(htmlData)
|
||||
|
||||
// ~~~~2.upload~~~~
|
||||
const { handleUploadSuccess, handleUploadError, parseChapter, selectValue } = useUpload(htmlData)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ref } from "vue"
|
||||
|
||||
export default function (crudOrFormRef: any) {
|
||||
export default function (crudOrFormRef?: any) {
|
||||
const crudColumns = ref([
|
||||
{
|
||||
title: "ID",
|
||||
|
||||
@@ -2,30 +2,29 @@
|
||||
<div class="ma-content-block lg:flex justify-between p-4">
|
||||
<div class="lg:w-full w-full lg:ml-4 mt-5 lg:mt-0">
|
||||
<!-- CRUD组件 -->
|
||||
<ma-crud
|
||||
id="basic-table-design-normal"
|
||||
:options="crudOptions"
|
||||
:columns="crudColumns"
|
||||
ref="crudRef"
|
||||
:parent-key="route.query.key"
|
||||
>
|
||||
<ma-crud id="basic-table-design-normal" :options="crudOptions" :columns="crudColumns" ref="crudRef" :parent-key="route.query.key">
|
||||
<template #ident="{ record }">
|
||||
{{ showType(record) }}
|
||||
</template>
|
||||
<template #tableAfterButtons>
|
||||
<a-space>
|
||||
<a-button
|
||||
status="success"
|
||||
type="outline"
|
||||
@click="handleAddFileInputDemand"
|
||||
v-if="isXQ === 'XQ'"
|
||||
>
|
||||
<a-button status="success" type="outline" @click="handleAddFileInputDemand" v-if="isXQ === 'XQ'">
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
上传需求规格说明快捷录入
|
||||
</a-button>
|
||||
<a-divider direction="vertical" type="double" />
|
||||
<a-dropdown-button type="primary" @click="handleBatchCreate">
|
||||
批量建设计需求
|
||||
<template #icon>
|
||||
<icon-down />
|
||||
</template>
|
||||
<template #content>
|
||||
<a-doption @click="handleBatchDemandCreate">批量创建测试项</a-doption>
|
||||
<a-doption @click="handleBatchCaseCreate">批量创建测试用例</a-doption>
|
||||
</template>
|
||||
</a-dropdown-button>
|
||||
<a-button type="outline" @click="handleReplaceClick">批量替换</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
@@ -49,6 +48,12 @@
|
||||
popup-key="design-normal"
|
||||
@replaceSuccess="replaceSuccessHandle"
|
||||
/>
|
||||
<!-- 批量新增设计需求组件 -->
|
||||
<BatchDesginCreate ref="batchCreateRef" :typeDict="demandTypeDict" @batchCreateFinish="refreshCrudTable" />
|
||||
<!-- 批量创建测试项组件 -->
|
||||
<BatchDemandCreate ref="batchCreateDemandRef" @batchDemandCreateComplete="refreshCrudTable" />
|
||||
<!-- 批量创建测试用例组件 -->
|
||||
<BatchCaseCreate ref="batchCreateCaseRef" @batchCaseCreateComplete="refreshCrudTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -63,6 +68,9 @@ import designApi from "@/api/project/designDemand"
|
||||
import commonApi from "@/api/common"
|
||||
import FileInputModal from "./components/FileInputModal/index.vue"
|
||||
import ReplaceModel from "@/views/project/opeSets/components/DesignTable/ReplaceModal.vue"
|
||||
import BatchDesginCreate from "@/views/project/components/BatchDesignCreate"
|
||||
import BatchDemandCreate from "@/views/project/components/BatchDemandCreate/index.vue"
|
||||
import BatchCaseCreate from "@/views/project/components/BatchCaseCreate/index.vue"
|
||||
|
||||
const route = useRoute()
|
||||
const crudRef = ref()
|
||||
@@ -70,6 +78,10 @@ const projectId = ref(route.query.id)
|
||||
|
||||
// 5月28日新增功能:替换
|
||||
const replaceModal = ref()
|
||||
// 12月16日新增功能:批量添加
|
||||
const batchCreateRef = ref()
|
||||
const batchCreateDemandRef = ref()
|
||||
const batchCreateCaseRef = ref()
|
||||
|
||||
const handleReplaceClick = () => {
|
||||
replaceModal.value?.open(crudRef.value.getSelecteds) // 把获取选中行的函数给传递给替换组件
|
||||
@@ -116,6 +128,21 @@ const handleAddFileInputDemand = () => {
|
||||
fileInputRef.value.open()
|
||||
}
|
||||
|
||||
// ~~~批量新增设计需求弹窗~~~
|
||||
const handleBatchCreate = () => {
|
||||
batchCreateRef.value.open({})
|
||||
}
|
||||
|
||||
// ~~~批量新增测试项弹窗~~~
|
||||
const handleBatchDemandCreate = () => {
|
||||
batchCreateDemandRef.value.open({})
|
||||
}
|
||||
|
||||
// ~~~批量新增测试用例弹窗~~~
|
||||
const handleBatchCaseCreate = () => {
|
||||
batchCreateCaseRef.value.open({})
|
||||
}
|
||||
|
||||
const refreshCrudTable = () => {
|
||||
crudRef.value.refresh()
|
||||
}
|
||||
|
||||
@@ -27,8 +27,10 @@ import useColumn from "@/views/project/dut/hooks/useColumns"
|
||||
|
||||
// refs
|
||||
const formRef = ref(null)
|
||||
|
||||
// 0.props-表示不通用代码
|
||||
const { designInfo } = defineProps<{ designInfo: any }>()
|
||||
|
||||
// 计算属性单独处理
|
||||
const designInfoJudge = computed(() => {
|
||||
return designInfo
|
||||
|
||||
@@ -3,11 +3,7 @@
|
||||
<search v-show="searchVisible" @submit="searchSubmit" />
|
||||
<div class="lg:flex justify-between mb-2">
|
||||
<a-space>
|
||||
<a-popconfirm
|
||||
content="确定要删除数据吗? 这会删除全部下级数据!"
|
||||
position="bottom"
|
||||
@ok="deletesMultipleAction"
|
||||
>
|
||||
<a-popconfirm content="确定要删除数据吗? 这会删除全部下级数据!" position="bottom" @ok="deletesMultipleAction">
|
||||
<a-button type="primary" status="danger">
|
||||
批量删除
|
||||
<template #icon><icon-delete /></template>
|
||||
@@ -62,11 +58,7 @@
|
||||
<template v-for="column in columns" :key="column.dataIndex">
|
||||
<template v-if="!column.hide">
|
||||
<!-- 正常的数据列 -->
|
||||
<a-table-column
|
||||
v-bind="column"
|
||||
v-if="!column.showType && column.dataIndex !== 'testContent'"
|
||||
tooltip
|
||||
>
|
||||
<a-table-column v-bind="column" v-if="!column.showType && column.dataIndex !== 'testContent'" tooltip>
|
||||
<!-- 如果column有isHyperText属性,则直接渲染html -->
|
||||
<template #cell="{ record }" v-if="column.isHyperText">
|
||||
<div v-html="record[column.dataIndex]"></div>
|
||||
@@ -79,15 +71,12 @@
|
||||
<!-- 如果有测试子项即subStep -->
|
||||
<template v-for="(sub, idx) in record[column.dataIndex]" :key="idx">
|
||||
<!-- 这是每个测试子项 -->
|
||||
<div class="subTitle mt-1">{{ idx + 1 }}.{{ sub.subName }}</div>
|
||||
<div class="subTitle mt-3">{{ idx + 1 }}.{{ sub.subName }}</div>
|
||||
<div>测试子项描述:{{ sub.subDescription }}</div>
|
||||
<template v-for="(step, index) in sub.subStep" :key="index">
|
||||
<span class="text-amber-700">步骤{{ index + 1 }})</span>
|
||||
<div class="operation">
|
||||
<span class="text-bold">操作:</span>{{ step.operation }}
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<span class="text-bold">预期:</span>{{ step.expect }}
|
||||
</div>
|
||||
<div class="operation"><span class="text-bold">操作:</span>{{ step.operation }}</div>
|
||||
<div class="mb-1"><span class="text-bold">预期:</span>{{ step.expect }}</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
@@ -247,10 +236,7 @@ const showType = useShowType("priority")
|
||||
const showTestType = useShowType("testType")
|
||||
|
||||
// 3.query查询和分页相关
|
||||
const { tableData, isFetching, fetchData, total, pageChange, pageSizeChange, searchParams } = useFetchData(
|
||||
demandApi.getTestDemandList,
|
||||
columns
|
||||
)
|
||||
const { tableData, isFetching, fetchData, total, pageChange, pageSizeChange, searchParams } = useFetchData(demandApi.getTestDemandList, columns)
|
||||
|
||||
// 4.表单相关
|
||||
const formRef = ref<InstanceType<typeof Form> | null>(null)
|
||||
|
||||
@@ -40,7 +40,7 @@ const DutSubForm = defineComponent({
|
||||
// 注意v-model:visible是不能放在对象解构的
|
||||
<a-modal {...modalOptions} v-model:visible={visible.value} unmount-on-close>
|
||||
{{
|
||||
title: () => <span>[被测件]-{title.value}</span>,
|
||||
title: () => <span >[被测件]-{title.value}</span>,
|
||||
default: () => (
|
||||
<ma-form
|
||||
ref={formRef}
|
||||
|
||||
@@ -9,7 +9,7 @@ const beiceType: BeiceTypeT[] = [
|
||||
{ label: "设计说明", value: "SJ" },
|
||||
{ label: "需求文档", value: "XQ" },
|
||||
{ label: "通信协议", value: "XY" },
|
||||
{ label: "研制总要求/技术协议等", value: "YZ" }
|
||||
{ label: "研制总要求/技术协议/顶层任务书等", value: "YZ" }
|
||||
]
|
||||
|
||||
export default beiceType
|
||||
|
||||
@@ -8,15 +8,11 @@ import useBeforeCancel from "@/views/project/projPublicHooks/useBeforeCancel"
|
||||
import { cloneDeep } from "lodash-es"
|
||||
|
||||
const CaseSubForm = defineComponent({
|
||||
name: "DemandSubFormForm",
|
||||
name: "CaseSubFrom",
|
||||
setup(_, { expose }) {
|
||||
// hook variable
|
||||
const treeDataStore = useTreeDataStore()
|
||||
const { title, formData, formRef, modalOptions, project_id, visible } = subFormHooks(
|
||||
caseApi.update,
|
||||
treeDataStore.updateCaseTreeData,
|
||||
"80%"
|
||||
)
|
||||
const { title, formData, formRef, modalOptions, project_id, visible } = subFormHooks(caseApi.update, treeDataStore.updateCaseTreeData, "80%")
|
||||
// hooks
|
||||
const { options, columnOptions } = useOptions(formRef) // **option里面变化**
|
||||
// 双击打开回调
|
||||
@@ -26,7 +22,7 @@ const CaseSubForm = defineComponent({
|
||||
const key = nodeData.key as string
|
||||
// 设置表单名称
|
||||
title.value = nodeData.title!
|
||||
// 注意这里因为case接口原因,这里需要projectId!!!!!!!!!!!!!!!
|
||||
// 注意这里因为case接口原因,这里需要projectId!
|
||||
const res = await caseApi.getCaseOne({ projectId: project_id, key }) // **API变化**
|
||||
// 得到数据时候将beforeFormContent搞定
|
||||
beforeFormContent.value = cloneDeep(res.data.testStep)
|
||||
@@ -52,23 +48,10 @@ const CaseSubForm = defineComponent({
|
||||
// Dom
|
||||
return () => (
|
||||
// 注意v-model:visible是不能放在对象解构的
|
||||
<a-modal
|
||||
{...modalOptions}
|
||||
v-model:visible={visible.value}
|
||||
on-before-cancel={handleBeforeCancel}
|
||||
width="86%"
|
||||
unmount-on-close
|
||||
>
|
||||
<a-modal {...modalOptions} v-model:visible={visible.value} on-before-cancel={handleBeforeCancel} width="86%" unmount-on-close>
|
||||
{{
|
||||
title: () => <span>[测试用例]-{title.value}</span>,
|
||||
default: () => (
|
||||
<ma-form
|
||||
ref={formRef}
|
||||
v-model={formData.value}
|
||||
options={options.value}
|
||||
columns={columnOptions.value}
|
||||
></ma-form>
|
||||
)
|
||||
default: () => <ma-form ref={formRef} v-model={formData.value} options={options.value} columns={columnOptions.value}></ma-form>
|
||||
}}
|
||||
</a-modal>
|
||||
)
|
||||
|
||||
@@ -361,7 +361,7 @@ const useCrudInit = function () {
|
||||
{
|
||||
title: "依据标准",
|
||||
dataIndex: "standard",
|
||||
addDefaultValue: ["16", "2", "17", "3", "7", "4", "5", "6"],
|
||||
addDefaultValue: ["16", "17", "3", "7", "4", "5", "18", "6", "19"],
|
||||
maxTagCount: 20,
|
||||
commonRules: [{ required: true, message: "请至少选择一个" }],
|
||||
hide: true,
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user