Compare commits
10 Commits
09fe532bb6
...
a5abc874ab
| Author | SHA1 | Date | |
|---|---|---|---|
| a5abc874ab | |||
| ec972fac8e | |||
| 92783045ef | |||
| 3bb7e027a6 | |||
| a0dc6bd074 | |||
| 03aed85d86 | |||
| 80ef6f7ce8 | |||
| 47e3c5f637 | |||
| 8b2c34d70b | |||
| aa0290b345 |
@@ -1,2 +1,2 @@
|
|||||||
# cdTestPlant3
|
# cdTestPlant3
|
||||||
里面有3个项目,中间那个是正确的
|
V0.0.1.0增加AI生成ce项功能
|
||||||
|
|||||||
@@ -4,3 +4,5 @@ VITE_APP_ENV = development
|
|||||||
VITE_APP_BASE_URL = http://127.0.0.1:8000/api
|
VITE_APP_BASE_URL = http://127.0.0.1:8000/api
|
||||||
VITE_APP_WS_URL = ws://127.0.0.1:8000/message.io
|
VITE_APP_WS_URL = ws://127.0.0.1:8000/message.io
|
||||||
VITE_APP_PROXY_PREFIX = /api
|
VITE_APP_PROXY_PREFIX = /api
|
||||||
|
# AI
|
||||||
|
VUE_APP_AI_API_BASE = http://192.168.0.63:8777
|
||||||
|
|||||||
@@ -3,3 +3,5 @@ VITE_APP_ENV = production
|
|||||||
VITE_APP_BASE_URL = http://127.0.0.1:8000/api
|
VITE_APP_BASE_URL = http://127.0.0.1:8000/api
|
||||||
VITE_APP_WS_URL = ws://127.0.0.1:8000/message.io
|
VITE_APP_WS_URL = ws://127.0.0.1:8000/message.io
|
||||||
VITE_APP_PROXY_PREFIX = /api
|
VITE_APP_PROXY_PREFIX = /api
|
||||||
|
# AI
|
||||||
|
VUE_APP_AI_API_BASE = http://192.168.0.63:8777
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"printWidth": 120,
|
"printWidth": 160,
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"semi": false
|
"semi": false
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"ignoreDeprecations": "6.0",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": [
|
||||||
|
|||||||
3850
cdTMP/package-lock.json
generated
3850
cdTMP/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "cdtmp",
|
"name": "testplant",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.5",
|
"version": "0.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -13,54 +13,53 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@arco-design/color": "^0.4.0",
|
"@arco-design/color": "^0.4.0",
|
||||||
"@arco-design/web-vue": "^2.57.0",
|
"@arco-design/web-vue": "^2.57.0",
|
||||||
"@tanstack/vue-query": "^5.76.0",
|
"@tanstack/vue-query": "^5.92.9",
|
||||||
"@tinymce/tinymce-vue": "^6.1.0",
|
"@tinymce/tinymce-vue": "^6.3.0",
|
||||||
"@vueuse/core": "^13.2.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.13.4",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.19",
|
||||||
"file2md5": "^1.3.0",
|
"file2md5": "^1.3.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.23",
|
||||||
"lucide-vue-next": "^0.509.0",
|
"mammoth": "^1.11.0",
|
||||||
"mammoth": "^1.9.0",
|
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.4",
|
||||||
"pinyin-match": "^1.2.8",
|
"pinyin-match": "^1.2.10",
|
||||||
"postcss-import": "^16.1.0",
|
"postcss-import": "^16.1.1",
|
||||||
"qs": "^6.14.0",
|
"qs": "^6.14.1",
|
||||||
"tailwind-merge": "^3.3.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tinymce": "^7.8.0",
|
"tinymce": "^7.9.1",
|
||||||
"tw-animate-css": "^1.2.9",
|
"tw-animate-css": "^1.4.0",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.27",
|
||||||
"vue-clipboard3": "^2.0.0",
|
"vue-clipboard3": "^2.0.0",
|
||||||
"vue-color-kit": "^1.0.6",
|
"vue-color-kit": "^1.0.6",
|
||||||
"vue-data-ui": "^2.6.45",
|
"vue-data-ui": "^3.13.4",
|
||||||
"vue-router": "^4.5.1",
|
"vue-router": "^4.6.4",
|
||||||
"vuedraggable": "^2.24.3"
|
"vuedraggable": "^2.24.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.6",
|
"@tailwindcss/postcss": "^4.1.18",
|
||||||
"@tailwindcss/vite": "^4.1.6",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^22.15.18",
|
"@types/node": "^25.0.10",
|
||||||
"@types/nprogress": "^0.2.3",
|
"@types/nprogress": "^0.2.3",
|
||||||
"@types/qs": "^6.9.18",
|
"@types/qs": "^6.14.0",
|
||||||
"@vitejs/plugin-vue": "^5.2.4",
|
"@vitejs/plugin-vue": "^6.0.3",
|
||||||
"@vitejs/plugin-vue-jsx": "^4.1.2",
|
"@vitejs/plugin-vue-jsx": "^5.1.3",
|
||||||
"@vue/babel-plugin-jsx": "^1.4.0",
|
"@vue/babel-plugin-jsx": "^2.0.1",
|
||||||
"browserslist": "^4.24.5",
|
"browserslist": "^4.28.1",
|
||||||
"eslint": "^9.26.0",
|
"eslint": "^9.39.2",
|
||||||
"eslint-plugin-vue": "^10.1.0",
|
"eslint-plugin-vue": "^10.7.0",
|
||||||
"less": "^4.3.0",
|
"less": "^4.5.1",
|
||||||
"less-loader": "^12.3.0",
|
"less-loader": "^12.3.0",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.8.1",
|
||||||
"rollup-plugin-visualizer": "^5.14.0",
|
"rollup-plugin-visualizer": "^6.0.5",
|
||||||
"tailwindcss": "^4.1.6",
|
"tailwindcss": "^4.1.18",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^6.3.5",
|
"vite": "^7.3.1",
|
||||||
"vue-eslint-parser": "^10.1.3"
|
"vue-eslint-parser": "^10.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const storageEvent = function (e: StorageEvent) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("storage", storageEvent)
|
window.addEventListener("storage", storageEvent)
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener("storage", storageEvent)
|
window.removeEventListener("storage", storageEvent)
|
||||||
|
|||||||
23
cdTMP/src/api/outs/aiApi.ts
Normal file
23
cdTMP/src/api/outs/aiApi.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { request } from "@/api/request"
|
||||||
|
|
||||||
|
const AI_API_BASE = import.meta.env.VUE_APP_AI_API_BASE || "http://192.168.0.63:8777"
|
||||||
|
|
||||||
|
interface DataRowType {
|
||||||
|
question: string
|
||||||
|
stream: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 请求AI生成测试项
|
||||||
|
* @returns 可流式或一次性
|
||||||
|
*/
|
||||||
|
getAiTestItem(data: DataRowType) {
|
||||||
|
return request({
|
||||||
|
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
|
data: params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 添加测试用例
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
batchSave(data = {}) {
|
||||||
|
return request({
|
||||||
|
url: "/project/case/multi_save",
|
||||||
|
method: "post",
|
||||||
|
data
|
||||||
|
})
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 更新测试用例
|
* 更新测试用例
|
||||||
* @returns
|
* @returns
|
||||||
@@ -125,7 +136,7 @@ export default {
|
|||||||
* 批量替换事件
|
* 批量替换事件
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
exetimeReplace(data = { selectRows: [], exetime: "" }) {
|
exetimeReplace(data = { selectRows: [], exetime: [] }) {
|
||||||
return request({
|
return request({
|
||||||
url: "/project/case/timeReplace/",
|
url: "/project/case/timeReplace/",
|
||||||
method: "post",
|
method: "post",
|
||||||
|
|||||||
@@ -59,11 +59,11 @@ export default {
|
|||||||
* 批量添加设计需求
|
* 批量添加设计需求
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
multiSave(params = {}) {
|
multiSave(data = {}) {
|
||||||
return request({
|
return request({
|
||||||
url: "/project/designDemand/multi_save",
|
url: "/project/designDemand/multi_save",
|
||||||
method: "post",
|
method: "post",
|
||||||
data: params
|
data
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -109,5 +109,38 @@ export default {
|
|||||||
method: "post",
|
method: "post",
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 自动生成人机交互界面测试5个测试项和用例
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
create_rj(params = {}) {
|
||||||
|
return request({
|
||||||
|
url: "/project/create_renji/",
|
||||||
|
method: "get",
|
||||||
|
params
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 复制到当前dut下面,CRUD中操作列
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
copy_current(params = {}) {
|
||||||
|
return request({
|
||||||
|
url: "/project/copy_current",
|
||||||
|
method: "get",
|
||||||
|
params
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 拖拽设计需求排序
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
switch_position(from_key, to_key, pos, project_id) {
|
||||||
|
return request({
|
||||||
|
url: "/project/switch_position",
|
||||||
|
method: "get",
|
||||||
|
params: { from_key, to_key, pos, project_id }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 根据项目名、树节点等级和key查找设计需求
|
* 根据项目名、树节点等级和key查找测试项
|
||||||
* @returns 设计需求树状节点信息
|
* @returns 设计需求树状节点信息
|
||||||
*/
|
*/
|
||||||
getDemandInfo(projectId, key, level) {
|
getDemandInfo(projectId, key, level) {
|
||||||
@@ -42,8 +42,22 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 根据项目名、树节点等级和key查找测试需求
|
* 根据项目id和轮次key
|
||||||
* @returns 返回测试需求testDemand
|
* @returns 测试项级联数据
|
||||||
|
*/
|
||||||
|
getRoundRelatedDemand(id, round) {
|
||||||
|
return request({
|
||||||
|
url: `project/getRelatedTestDemand`,
|
||||||
|
method: "get",
|
||||||
|
params: {
|
||||||
|
id,
|
||||||
|
round
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 根据项目名、树节点等级和key查找测试项
|
||||||
|
* @returns 返回测试项testDemand
|
||||||
*/
|
*/
|
||||||
getTestInfo(projectId, key, level) {
|
getTestInfo(projectId, key, level) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 添加被测件
|
* 添加测试项
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
save(params = {}) {
|
save(params = {}) {
|
||||||
@@ -43,6 +43,17 @@ export default {
|
|||||||
data: params
|
data: params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* 批量新增添加测试项
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
batchSave(data = {}) {
|
||||||
|
return request({
|
||||||
|
url: "/project/testDemand/multi_save",
|
||||||
|
method: "post",
|
||||||
|
data
|
||||||
|
})
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 更新dut
|
* 更新dut
|
||||||
* @returns
|
* @returns
|
||||||
@@ -119,5 +130,16 @@ export default {
|
|||||||
method: "post",
|
method: "post",
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 选择行后,批量修改“优先级”
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
priorityReplace(data = { selectRows: [], priority: "" }) {
|
||||||
|
return request({
|
||||||
|
url: "/project/testDemand/priorityReplace/",
|
||||||
|
method: "post",
|
||||||
|
data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
81
cdTMP/src/components/ai-button/index.vue
Normal file
81
cdTMP/src/components/ai-button/index.vue
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<style scoped>
|
||||||
|
button {
|
||||||
|
position: relative;
|
||||||
|
padding: 10px 20px;
|
||||||
|
|
||||||
|
border-radius: 0px;
|
||||||
|
border: 1px solid rgb(61, 106, 255);
|
||||||
|
font-size: 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
background: transparent;
|
||||||
|
color: rgb(61, 106, 255);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 0 0 0 transparent;
|
||||||
|
-webkit-transition: all 0.2s ease-in;
|
||||||
|
-moz-transition: all 0.2s ease-in;
|
||||||
|
transition: all 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: rgb(61, 106, 255);
|
||||||
|
box-shadow: 0 0 30px 5px rgba(0, 142, 236, 0.815);
|
||||||
|
-webkit-transition: all 0.2s ease-out;
|
||||||
|
-moz-transition: all 0.2s ease-out;
|
||||||
|
transition: all 0.2s ease-out;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover::before {
|
||||||
|
-webkit-animation: sh02 0.5s 0s linear;
|
||||||
|
-moz-animation: sh02 0.5s 0s linear;
|
||||||
|
animation: sh02 0.5s 0s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
button::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 0px;
|
||||||
|
height: 86%;
|
||||||
|
position: absolute;
|
||||||
|
top: 7%;
|
||||||
|
left: 0%;
|
||||||
|
opacity: 0;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 0 50px 30px #fff;
|
||||||
|
-webkit-transform: skewX(-20deg);
|
||||||
|
-moz-transform: skewX(-20deg);
|
||||||
|
-ms-transform: skewX(-20deg);
|
||||||
|
-o-transform: skewX(-20deg);
|
||||||
|
transform: skewX(-20deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sh02 {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
left: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
box-shadow: 0 0 0 0 transparent;
|
||||||
|
-webkit-transition: box-shadow 0.2s ease-in;
|
||||||
|
-moz-transition: box-shadow 0.2s ease-in;
|
||||||
|
transition: box-shadow 0.2s ease-in;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button>AI生成测试项</button>
|
||||||
|
</template>
|
||||||
@@ -99,11 +99,16 @@ const submit = async () => {
|
|||||||
if (isFunction(options.beforeEdit) && !(await options.beforeEdit(formData))) {
|
if (isFunction(options.beforeEdit) && !(await options.beforeEdit(formData))) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// 修改源码2025-06-24,使用try...catch捕获信息
|
||||||
|
try {
|
||||||
if (!options.parameters) {
|
if (!options.parameters) {
|
||||||
response = await options.edit.api(formData[options.pk], formData)
|
response = await options.edit.api(formData[options.pk], formData)
|
||||||
} else {
|
} else {
|
||||||
response = await options.edit.api(formData[options.pk], { ...formData, ...options.parameters })
|
response = await options.edit.api(formData[options.pk], { ...formData, ...options.parameters })
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
isFunction(options.afterEdit) && (await options.afterEdit(response, formData))
|
isFunction(options.afterEdit) && (await options.afterEdit(response, formData))
|
||||||
}
|
}
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
<!--
|
|
||||||
- @Author XXX
|
|
||||||
- @Link XXX
|
|
||||||
-->
|
|
||||||
<template>
|
<template>
|
||||||
<a-modal v-model:visible="visible" :footer="false" @cancel="close" draggable>
|
<a-modal v-model:visible="visible" :footer="false" @cancel="close" draggable>
|
||||||
<template #title>导入</template>
|
<template #title>导入</template>
|
||||||
|
|||||||
@@ -41,6 +41,11 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
<a-divider
|
||||||
|
:style="{
|
||||||
|
margin: '8px 0'
|
||||||
|
}"
|
||||||
|
></a-divider>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -279,7 +279,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import config from "@/config/crud"
|
import config from "@/config/crud"
|
||||||
import { ref, watch, provide, nextTick, onMounted, onUnmounted } from "vue"
|
import { ref, watch, provide, nextTick, onMounted, onUnmounted, computed } from "vue"
|
||||||
import defaultOptions from "./js/defaultOptions"
|
import defaultOptions from "./js/defaultOptions"
|
||||||
import { loadDict } from "@cps/ma-form/js/networkRequest.js"
|
import { loadDict } from "@cps/ma-form/js/networkRequest.js"
|
||||||
import ColumnService from "@cps/ma-form/js/columnService"
|
import ColumnService from "@cps/ma-form/js/columnService"
|
||||||
@@ -728,6 +728,11 @@ const setSelecteds = (key) => {
|
|||||||
selecteds.value = key
|
selecteds.value = key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修改源码,获取selected的值
|
||||||
|
const getSelecteds = () => {
|
||||||
|
return selecteds.value
|
||||||
|
}
|
||||||
|
|
||||||
const switchDataType = async () => {
|
const switchDataType = async () => {
|
||||||
isRecovery.value = !isRecovery.value
|
isRecovery.value = !isRecovery.value
|
||||||
currentApi.value =
|
currentApi.value =
|
||||||
@@ -929,7 +934,8 @@ defineExpose({
|
|||||||
crudSearchRef,
|
crudSearchRef,
|
||||||
crudImportRef,
|
crudImportRef,
|
||||||
crudSettingRef,
|
crudSettingRef,
|
||||||
setTableData
|
setTableData,
|
||||||
|
getSelecteds
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default {
|
|||||||
// 请求参数
|
// 请求参数
|
||||||
requestParams: {},
|
requestParams: {},
|
||||||
// 设置分页组件每页记录数
|
// 设置分页组件每页记录数
|
||||||
pageSizeOption: [10, 20, 30, 50, 100],
|
pageSizeOption: [10, 20, 30, 50, 1000],
|
||||||
// 是否开启表格分页
|
// 是否开启表格分页
|
||||||
tablePagination: false,
|
tablePagination: false,
|
||||||
// 设置选择列
|
// 设置选择列
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import "tinymce/plugins/nonbreaking" // 插入不间断空格
|
|||||||
// import "tinymce/plugins/preview" // 预览
|
// import "tinymce/plugins/preview" // 预览
|
||||||
import "tinymce/plugins/quickbars" // 快速工具栏
|
import "tinymce/plugins/quickbars" // 快速工具栏
|
||||||
import "tinymce/plugins/save" // 保存
|
import "tinymce/plugins/save" // 保存
|
||||||
import "tinymce/plugins/searchreplace" // 查找替换
|
// import "tinymce/plugins/searchreplace" // 查找替换
|
||||||
import "tinymce/plugins/table" // 表格
|
import "tinymce/plugins/table" // 表格
|
||||||
// import "tinymce/plugins/visualblocks" //显示元素范围
|
// import "tinymce/plugins/visualblocks" //显示元素范围
|
||||||
import "tinymce/plugins/visualchars" // 显示不可见字符
|
import "tinymce/plugins/visualchars" // 显示不可见字符
|
||||||
@@ -49,18 +49,20 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: () => "tinymce" + new Date().getTime().toString() + "-" + Math.random().toString(16).substring(2, 10)
|
default: () => "tinymce" + new Date().getTime().toString() + "-" + Math.random().toString(16).substring(2, 10)
|
||||||
},
|
},
|
||||||
|
// 插件
|
||||||
plugins: {
|
plugins: {
|
||||||
type: [String, Array],
|
type: [String, Array],
|
||||||
default: "searchreplace visualchars code table nonbreaking lists autosave autoresize"
|
default: "visualchars code table nonbreaking lists autosave autoresize"
|
||||||
|
// 备份:"searchreplace visualchars code table nonbreaking lists autosave autoresize"
|
||||||
},
|
},
|
||||||
|
// 工具栏
|
||||||
toolbar: {
|
toolbar: {
|
||||||
type: [String, Array],
|
type: [String, Array],
|
||||||
// 如果要取消粘贴只粘贴文本,需要用户加格式请加上pastetext
|
// 如果要取消粘贴只粘贴文本,需要用户加格式请加上pastetext
|
||||||
default:
|
default: "code undo redo aligncenter alignleft indent styleselect formatselect fontselect fontsizeselect removeformat"
|
||||||
"code undo redo aligncenter alignleft indent styleselect formatselect fontselect fontsizeselect removeformat"
|
|
||||||
|
|
||||||
// 下面是备份配置:
|
// 下面是备份配置:
|
||||||
// default:"code undo redo restoredraft | paste | bold | aligncenter alignleft alignjustify indent | \
|
// default:"code undo redo restoredraft | paste | bold | aligncenter alignleft alignjustify indent searchreplace | \
|
||||||
// styleselect formatselect fontselect fontsizeselect | bullist numlist | removeformat"
|
// styleselect formatselect fontselect fontsizeselect | bullist numlist | removeformat"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -151,13 +153,11 @@ const initConfig = reactive({
|
|||||||
min_height: 100,
|
min_height: 100,
|
||||||
max_height: 600,
|
max_height: 600,
|
||||||
autoresize_bottom_margin: 10,
|
autoresize_bottom_margin: 10,
|
||||||
content_css:
|
content_css: theme.value === "dark" ? "/tinymce/skins/content/dark/content.css" : "/tinymce/skins/content/default/content.css",
|
||||||
theme.value === "dark"
|
|
||||||
? "/tinymce/skins/content/dark/content.css"
|
|
||||||
: "/tinymce/skins/content/default/content.css",
|
|
||||||
// selector: "#textarea1", // 下面自定义样式选中的区域为编辑区
|
// selector: "#textarea1", // 下面自定义样式选中的区域为编辑区
|
||||||
content_style: "body {line-height:1.5;font-size:14px;} p {margin:2px 0px;}", // 这里可以设置自定义样式
|
content_style: "body {line-height:1.5;font-size:14px;} p {margin:2px 0px;}", // 这里可以设置自定义样式
|
||||||
// paste_as_text: false, // 粘贴文字只能是纯文本
|
// paste_as_text: false,
|
||||||
|
// 粘贴文字只能是纯文本
|
||||||
// 1.自定义粘贴过滤器函数,args.content就是粘贴内容
|
// 1.自定义粘贴过滤器函数,args.content就是粘贴内容
|
||||||
paste_preprocess: function (plugin, args) {
|
paste_preprocess: function (plugin, args) {
|
||||||
let content = args.content
|
let content = args.content
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-modal
|
<a-modal
|
||||||
:width="prop.width"
|
:width="prop.width"
|
||||||
:fullscreen="isFull"
|
:fullscreen="prop.isFull"
|
||||||
v-model:visible="modal.visible"
|
v-model:visible="modal.visible"
|
||||||
:on-before-ok="modal.submit"
|
:on-before-ok="modal.submit"
|
||||||
unmount-on-close
|
unmount-on-close
|
||||||
|
|||||||
93
cdTMP/src/components/ma-form/Customs/BulkReplaceModal.vue
Normal file
93
cdTMP/src/components/ma-form/Customs/BulkReplaceModal.vue
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<script lang="tsx">
|
||||||
|
import { defineComponent, ref } from "vue"
|
||||||
|
import { Modal, Form, FormItem, Input, Space, Button, Alert, Message } from "@arco-design/web-vue"
|
||||||
|
// 导入替换白名单
|
||||||
|
import handleSubmit from "./replaceFieldName"
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "BulkPeplaceModal",
|
||||||
|
props: {
|
||||||
|
parentForm: { type: Object, required: true }
|
||||||
|
},
|
||||||
|
setup(props, { expose }) {
|
||||||
|
// ref
|
||||||
|
const formRef = ref()
|
||||||
|
// refs
|
||||||
|
const visible = ref(false)
|
||||||
|
const formData = ref({
|
||||||
|
originText: "",
|
||||||
|
replaceText: ""
|
||||||
|
})
|
||||||
|
|
||||||
|
// inner-functions
|
||||||
|
const submit = (record: { values: any; error: any }) => {
|
||||||
|
handleSubmit(record, props.parentForm, formData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// obj-functions
|
||||||
|
const open = () => {
|
||||||
|
// 如果已经打开则关闭,清空查询
|
||||||
|
formData.value = {
|
||||||
|
originText: "",
|
||||||
|
replaceText: ""
|
||||||
|
}
|
||||||
|
visible.value = visible.value === true ? false : true
|
||||||
|
}
|
||||||
|
|
||||||
|
// expose
|
||||||
|
expose({ open })
|
||||||
|
return () => (
|
||||||
|
<div class="bulk-replace-container">
|
||||||
|
<Modal
|
||||||
|
draggable
|
||||||
|
unmount-on-close
|
||||||
|
title="替换当前页面文本"
|
||||||
|
width="500px"
|
||||||
|
v-model:visible={visible.value}
|
||||||
|
mask={false}
|
||||||
|
popup-container="#form-main-id"
|
||||||
|
modal-class="modal-container-custom-shadow"
|
||||||
|
modalAnimationName="fade"
|
||||||
|
footer={false}
|
||||||
|
>
|
||||||
|
<Alert type="warning" class="mb-2">
|
||||||
|
无法替换时间等非文本字段
|
||||||
|
</Alert>
|
||||||
|
<Form model={formData.value} onSubmit={submit} ref={formRef}>
|
||||||
|
<FormItem
|
||||||
|
field="originText"
|
||||||
|
label="被替文本"
|
||||||
|
validate-trigger={["change", "blur"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入查找和被替换的文本"
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入被替换文本" v-model={formData.value.originText}></Input>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem field="replaceText" label="被替换为" validate-trigger={["change", "blur"]}>
|
||||||
|
<Input placeholder="请输入替换的文本" v-model={formData.value.replaceText}></Input>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem>
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" html-type="submit">
|
||||||
|
全部替换
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => formRef.value.resetFields()}>重置</Button>
|
||||||
|
</Space>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.modal-container-custom-shadow {
|
||||||
|
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,9 +8,7 @@
|
|||||||
<th class="arco-table-th" :width="20">
|
<th class="arco-table-th" :width="20">
|
||||||
<span class="arco-table-cell arco-table-cell-align-center">
|
<span class="arco-table-cell arco-table-cell-align-center">
|
||||||
<a-tooltip content="添加步骤">
|
<a-tooltip content="添加步骤">
|
||||||
<a-button type="primary" size="mini" shape="round" @click="addItem">
|
<a-button :tabindex="-1" type="primary" size="mini" shape="round" @click="addItem"> 新增步骤+ </a-button>
|
||||||
新增步骤+
|
|
||||||
</a-button>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
@@ -28,16 +26,13 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- 根据subStep渲染 -->
|
<!-- 根据subStep渲染 -->
|
||||||
<template
|
<template v-if="modelValue && modelValue.length > 0" v-for="(stepItem, index) in modelValue" :key="index">
|
||||||
v-if="modelValue && modelValue.length > 0"
|
|
||||||
v-for="(stepItem, index) in modelValue"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<tr class="arco-table-tr">
|
<tr class="arco-table-tr">
|
||||||
<td class="arco-table-td">
|
<td class="arco-table-td">
|
||||||
<span class="arco-table-cell justify-center gap-1.5">
|
<span class="arco-table-cell justify-center gap-1.5">
|
||||||
<!-- 删除单项按钮 -->
|
<!-- 删除单项按钮 -->
|
||||||
<a-button
|
<a-button
|
||||||
|
:tabindex="-1"
|
||||||
type="primary"
|
type="primary"
|
||||||
status="danger"
|
status="danger"
|
||||||
size="mini"
|
size="mini"
|
||||||
@@ -48,23 +43,17 @@
|
|||||||
<template #icon><icon-close /></template>
|
<template #icon><icon-close /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-tooltip content="复制该项添加">
|
<a-tooltip content="复制该项添加">
|
||||||
<a-button
|
<a-button :tabindex="-1" type="primary" status="warning" size="mini" shape="round" @click="copyItem(index)">
|
||||||
type="primary"
|
|
||||||
status="warning"
|
|
||||||
size="mini"
|
|
||||||
shape="round"
|
|
||||||
@click="copyItem(index)"
|
|
||||||
>
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-copy />
|
<icon-copy />
|
||||||
</template>
|
</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-button-group shape="round" size="mini">
|
<a-button-group shape="round" size="mini">
|
||||||
<a-button type="primary" @click="moveUp(index)">
|
<a-button :tabindex="-1" type="primary" @click="moveUp(index)">
|
||||||
<icon-arrow-rise />
|
<icon-arrow-rise />
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" @click="moveDown(index)">
|
<a-button :tabindex="-1" type="primary" @click="moveDown(index)">
|
||||||
<icon-arrow-fall />
|
<icon-arrow-fall />
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-button-group>
|
</a-button-group>
|
||||||
@@ -85,7 +74,7 @@
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
<div class="flex justify-center items-center p-2 border-1">
|
<div class="flex justify-center items-center p-2 border">
|
||||||
<a-alert>暂无测试子项条目,请添加</a-alert>
|
<a-alert>暂无测试子项条目,请添加</a-alert>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -100,6 +89,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Ref, nextTick } from "vue"
|
import { type Ref, nextTick } from "vue"
|
||||||
|
import { cloneDeep } from "lodash-es"
|
||||||
|
|
||||||
// 辅助函数:当modelValue不是列表时候
|
// 辅助函数:当modelValue不是列表时候
|
||||||
function handleIsNotArray() {
|
function handleIsNotArray() {
|
||||||
@@ -129,7 +119,7 @@ const deleteItem = (index: number) => {
|
|||||||
|
|
||||||
// 复制单项
|
// 复制单项
|
||||||
const copyItem = (index: number) => {
|
const copyItem = (index: number) => {
|
||||||
const newItem = modelValue.value[index]
|
const newItem = cloneDeep(modelValue.value[index])
|
||||||
modelValue.value = [...modelValue.value, newItem]
|
modelValue.value = [...modelValue.value, newItem]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
73
cdTMP/src/components/ma-form/Customs/replaceFieldName.ts
Normal file
73
cdTMP/src/components/ma-form/Customs/replaceFieldName.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { Message } from "@arco-design/web-vue"
|
||||||
|
|
||||||
|
// 注意该白名单仅支持替换“字符串”and“数组”,其他不行!
|
||||||
|
const replaceField = [
|
||||||
|
"initialization", // 用例:初始化
|
||||||
|
"name", // 各个form的名称
|
||||||
|
"premise", // 用例:前提和约束
|
||||||
|
"summarize", // 用例:综述
|
||||||
|
"testStep", // 用例:步骤,数组包含{operation:"",expect:""}
|
||||||
|
"testDesciption", // 测试项:测试项描述
|
||||||
|
"testContent" // 测试项:子项
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleSubmit = (record: { values: any; error: any }, parentForm: any, formData: any) => {
|
||||||
|
if (record.error) return
|
||||||
|
// 替换逻辑
|
||||||
|
Object.keys(parentForm).forEach((fieldName) => {
|
||||||
|
if (replaceField.includes(fieldName)) {
|
||||||
|
// 如果存在白名单里面,在判断是文本还是数组
|
||||||
|
const lineData = parentForm[fieldName]
|
||||||
|
if (typeof lineData === "string") {
|
||||||
|
// string则直接替换
|
||||||
|
parentForm[fieldName] = parentForm[fieldName].replaceAll(
|
||||||
|
formData.value.originText,
|
||||||
|
formData.value.replaceText
|
||||||
|
)
|
||||||
|
} else if (Array.isArray(lineData)) {
|
||||||
|
parentForm[fieldName].forEach(
|
||||||
|
(obj: { operation: string; expect: string; result: string; subName: string; subStep: string }) => {
|
||||||
|
// 用例:数组只能替换对象operation、expect、result
|
||||||
|
if (obj.operation) {
|
||||||
|
obj.operation = obj.operation.replaceAll(
|
||||||
|
formData.value.originText,
|
||||||
|
formData.value.replaceText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (obj.expect) {
|
||||||
|
obj.expect = obj.expect.replaceAll(formData.value.originText, formData.value.replaceText)
|
||||||
|
}
|
||||||
|
if (obj.result) {
|
||||||
|
obj.result = obj.result.replaceAll(formData.value.originText, formData.value.replaceText)
|
||||||
|
}
|
||||||
|
// 测试项:嵌套两层
|
||||||
|
if (obj.subName) {
|
||||||
|
obj.subName = obj.subName.replaceAll(formData.value.originText, formData.value.replaceText)
|
||||||
|
if (obj.subStep && Array.isArray(obj.subStep)) {
|
||||||
|
obj.subStep.forEach((step) => {
|
||||||
|
if (step.operation) {
|
||||||
|
step.operation = step.operation.replaceAll(
|
||||||
|
formData.value.originText,
|
||||||
|
formData.value.replaceText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (step.expect) {
|
||||||
|
step.expect = step.expect.replaceAll(
|
||||||
|
formData.value.originText,
|
||||||
|
formData.value.replaceText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Message.success("替换成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handleSubmit
|
||||||
@@ -36,35 +36,30 @@
|
|||||||
<a-space>
|
<a-space>
|
||||||
<!-- 修改源码:添加向上和向下 -->
|
<!-- 修改源码:添加向上和向下 -->
|
||||||
<a-tooltip content="向上移动" v-if="!(props.component.hideAdd ?? false)">
|
<a-tooltip content="向上移动" v-if="!(props.component.hideAdd ?? false)">
|
||||||
<a-button @click.stop="moveUp(itemIndex)" type="primary" size="small" shape="round">
|
<a-button :tabindex="-1" @click.stop="moveUp(itemIndex)" type="primary" size="small" shape="round">
|
||||||
<template #icon><icon-arrow-rise /></template>
|
<template #icon><icon-arrow-rise /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip content="向下移动" v-if="!(props.component.hideAdd ?? false)">
|
<a-tooltip content="向下移动" v-if="!(props.component.hideAdd ?? false)">
|
||||||
<a-button @click.stop="moveDown(itemIndex)" type="primary" size="small" shape="round">
|
<a-button :tabindex="-1" @click.stop="moveDown(itemIndex)" type="primary" size="small" shape="round">
|
||||||
<template #icon><icon-arrow-fall /></template>
|
<template #icon><icon-arrow-fall /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<icon-oblique-line />
|
<icon-oblique-line />
|
||||||
<!-- 修改源码:新增复制该项新增 -->
|
<!-- 修改源码:新增复制该项新增 -->
|
||||||
<a-tooltip content="复制该项添加" v-if="!(props.component.hideAdd ?? false)">
|
<a-tooltip content="复制该项添加" v-if="!(props.component.hideAdd ?? false)">
|
||||||
<a-button
|
<a-button :tabindex="-1" @click.stop="addItem(item)" type="primary" size="small" shape="round" status="warning">
|
||||||
@click.stop="addItem(item)"
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
shape="round"
|
|
||||||
status="warning"
|
|
||||||
>
|
|
||||||
<template #icon><icon-copy /></template>
|
<template #icon><icon-copy /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip content="添加新子项" v-if="!(props.component.hideAdd ?? false)">
|
<a-tooltip content="添加新子项" v-if="!(props.component.hideAdd ?? false)">
|
||||||
<a-button @click.stop="addItem()" type="primary" size="small" shape="round">
|
<a-button :tabindex="-1" @click.stop="addItem()" type="primary" size="small" shape="round">
|
||||||
<template #icon><icon-plus /></template>
|
<template #icon><icon-plus /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip content="删除该子项" v-if="!(props.component.hideDelete ?? false)">
|
<a-tooltip content="删除该子项" v-if="!(props.component.hideDelete ?? false)">
|
||||||
<a-button
|
<a-button
|
||||||
|
:tabindex="-1"
|
||||||
@click.stop="deleteItem(itemIndex)"
|
@click.stop="deleteItem(itemIndex)"
|
||||||
:disabled="formModel[props.component.dataIndex].length === 1"
|
:disabled="formModel[props.component.dataIndex].length === 1"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -72,7 +67,7 @@
|
|||||||
shape="round"
|
shape="round"
|
||||||
status="danger"
|
status="danger"
|
||||||
>
|
>
|
||||||
<template #icon><icon-minus /></template>
|
<template #icon><icon-close /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-space>
|
</a-space>
|
||||||
@@ -99,7 +94,7 @@
|
|||||||
<tr class="arco-table-tr">
|
<tr class="arco-table-tr">
|
||||||
<th class="arco-table-th" width="60">
|
<th class="arco-table-th" width="60">
|
||||||
<span class="arco-table-cell arco-table-cell-align-center">
|
<span class="arco-table-cell arco-table-cell-align-center">
|
||||||
<a-button type="primary" @click="addItem()" size="small" shape="round">
|
<a-button :tabindex="-1" type="primary" @click="addItem()" size="small" shape="round">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-plus />
|
<icon-plus />
|
||||||
</template>
|
</template>
|
||||||
@@ -126,6 +121,7 @@
|
|||||||
<td class="arco-table-td">
|
<td class="arco-table-td">
|
||||||
<span class="arco-table-cell">
|
<span class="arco-table-cell">
|
||||||
<a-button
|
<a-button
|
||||||
|
:tabindex="-1"
|
||||||
type="primary"
|
type="primary"
|
||||||
status="danger"
|
status="danger"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -133,7 +129,7 @@
|
|||||||
:disabled="formModel[props.component.dataIndex].length === 1"
|
:disabled="formModel[props.component.dataIndex].length === 1"
|
||||||
@click="deleteItem(index)"
|
@click="deleteItem(index)"
|
||||||
>
|
>
|
||||||
<template #icon><icon-minus /></template>
|
<template #icon><icon-close /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -166,6 +162,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 修改源码:切换显示形态 -->
|
<!-- 修改源码:切换显示形态 -->
|
||||||
|
<Teleport to="body">
|
||||||
<a-popover>
|
<a-popover>
|
||||||
<template #title>切换{{ props.component.type === "group" ? "表格" : "聚合" }}显示</template>
|
<template #title>切换{{ props.component.type === "group" ? "表格" : "聚合" }}显示</template>
|
||||||
<div class="sticky-container" @click="swapTableOrGroupDisplay">
|
<div class="sticky-container" @click="swapTableOrGroupDisplay">
|
||||||
@@ -174,6 +171,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
|
</Teleport>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -210,8 +208,7 @@ watch(
|
|||||||
)
|
)
|
||||||
// ~~~~修改源码-end
|
// ~~~~修改源码-end
|
||||||
|
|
||||||
const rv = async (ev, value = undefined) =>
|
const rv = async (ev, value = undefined) => await runEvent(props.component, ev, { formModel, getColumnService, columns }, value)
|
||||||
await runEvent(props.component, ev, { formModel, getColumnService, columns }, value)
|
|
||||||
|
|
||||||
const defaultOpenKeys = [0]
|
const defaultOpenKeys = [0]
|
||||||
|
|
||||||
@@ -259,6 +256,18 @@ function swapItems(idx1, idx2) {
|
|||||||
;[arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]]
|
;[arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 辅助函数:触发充分性分析
|
||||||
|
// 2025-06-24:新增子项后需对触发更新
|
||||||
|
function updateChongFen() {
|
||||||
|
if (formModel.value.testContent && formModel.value.testContent.length > 0) {
|
||||||
|
// 如果有testContent则更新“充分性要求”
|
||||||
|
const subItemFormData = formModel.value.testContent
|
||||||
|
const mapRes = subItemFormData.map((subItem) => subItem.subName || "")
|
||||||
|
formModel.value.adequacy &&
|
||||||
|
(formModel.value.adequacy = `测试用例覆盖${mapRes.join("、")}子项要求的全部内容。\n所有用例执行完毕,对于未执行的用例说明未执行原因。`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 修改源码:上移动和下移动
|
// 修改源码:上移动和下移动
|
||||||
const moveUp = (itemIndex) => {
|
const moveUp = (itemIndex) => {
|
||||||
const itemLength = formModel.value[props.component.dataIndex].length
|
const itemLength = formModel.value[props.component.dataIndex].length
|
||||||
@@ -268,6 +277,8 @@ const moveUp = (itemIndex) => {
|
|||||||
}
|
}
|
||||||
// 进行移动
|
// 进行移动
|
||||||
swapItems(itemIndex, itemIndex - 1)
|
swapItems(itemIndex, itemIndex - 1)
|
||||||
|
// 2025-06-24修改
|
||||||
|
updateChongFen()
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveDown = (itemIndex) => {
|
const moveDown = (itemIndex) => {
|
||||||
@@ -278,6 +289,8 @@ const moveDown = (itemIndex) => {
|
|||||||
}
|
}
|
||||||
// 进行移动
|
// 进行移动
|
||||||
swapItems(itemIndex, itemIndex + 1)
|
swapItems(itemIndex, itemIndex + 1)
|
||||||
|
// 2025-06-24修改
|
||||||
|
updateChongFen()
|
||||||
}
|
}
|
||||||
|
|
||||||
const addItem = async (data = {}) => {
|
const addItem = async (data = {}) => {
|
||||||
@@ -287,6 +300,8 @@ const addItem = async (data = {}) => {
|
|||||||
viewFormList.value[index] = cloneDeep(formList)
|
viewFormList.value[index] = cloneDeep(formList)
|
||||||
rv("onAdd", { formList: viewFormList.value[index], newData, index }) // 修改源码:深度复制data->newData
|
rv("onAdd", { formList: viewFormList.value[index], newData, index }) // 修改源码:深度复制data->newData
|
||||||
formModel.value[props.component.dataIndex].push(newData) // 修改源码:深度复制data->newData
|
formModel.value[props.component.dataIndex].push(newData) // 修改源码:深度复制data->newData
|
||||||
|
// 修改源码,触发充分性更新
|
||||||
|
updateChongFen()
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteItem = async (index) => {
|
const deleteItem = async (index) => {
|
||||||
@@ -296,6 +311,8 @@ const deleteItem = async (index) => {
|
|||||||
await nextTick()
|
await nextTick()
|
||||||
formModel.value[props.component.dataIndex].splice(index, 1)
|
formModel.value[props.component.dataIndex].splice(index, 1)
|
||||||
}
|
}
|
||||||
|
// 2025-06-24修改
|
||||||
|
updateChongFen()
|
||||||
}
|
}
|
||||||
|
|
||||||
const getChildrenDataIndex = (index, dataIndex) => {
|
const getChildrenDataIndex = (index, dataIndex) => {
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
<!--
|
|
||||||
- @Author XXX
|
|
||||||
- @Link XXX
|
|
||||||
-->
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<slot :name="`form-${props.component.dataIndex}`" v-bind="props.component">
|
<slot :name="`form-${props.component.dataIndex}`" v-bind="props.component">
|
||||||
@@ -36,8 +32,7 @@ const props = defineProps({
|
|||||||
const formModel = inject("formModel")
|
const formModel = inject("formModel")
|
||||||
const getColumnService = inject("getColumnService")
|
const getColumnService = inject("getColumnService")
|
||||||
const columns = inject("columns")
|
const columns = inject("columns")
|
||||||
const rv = async (ev, value = undefined) =>
|
const rv = async (ev, value = undefined) => await runEvent(props.component, ev, { formModel, getColumnService, columns }, value)
|
||||||
await runEvent(props.component, ev, { formModel, getColumnService, columns }, value)
|
|
||||||
|
|
||||||
rv("onCreated")
|
rv("onCreated")
|
||||||
onMounted(() => rv("onMounted"))
|
onMounted(() => rv("onMounted"))
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
<!--
|
|
||||||
- @Author XXX
|
|
||||||
- @Link XXX
|
|
||||||
-->
|
|
||||||
<template>
|
<template>
|
||||||
<ma-form-item
|
<ma-form-item
|
||||||
v-if="typeof props.component.display == 'undefined' || props.component.display === true"
|
v-if="typeof props.component.display == 'undefined' || props.component.display === true"
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
<!--
|
|
||||||
- @Author XXX
|
|
||||||
- @Link XXX
|
|
||||||
-->
|
|
||||||
<template>
|
<template>
|
||||||
<ma-form-item
|
<ma-form-item
|
||||||
v-if="typeof props.component.display == 'undefined' || props.component.display === true"
|
v-if="typeof props.component.display == 'undefined' || props.component.display === true"
|
||||||
@@ -35,7 +31,7 @@
|
|||||||
><template #icon><icon-plus /></template
|
><template #icon><icon-plus /></template
|
||||||
></a-button>
|
></a-button>
|
||||||
<a-button size="small" type="primary" @click="minus(rowIndex)"
|
<a-button size="small" type="primary" @click="minus(rowIndex)"
|
||||||
><template #icon><icon-minus /></template
|
><template #icon><icon-close /></template
|
||||||
></a-button>
|
></a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
<!--
|
|
||||||
- @Author XXX
|
|
||||||
- @Link XXX
|
|
||||||
-->
|
|
||||||
<template>
|
<template>
|
||||||
<ma-form-item
|
<ma-form-item
|
||||||
v-if="typeof props.component.display == 'undefined' || props.component.display === true"
|
v-if="typeof props.component.display == 'undefined' || props.component.display === true"
|
||||||
@@ -42,13 +38,10 @@ const dictList = inject("dictList")
|
|||||||
const formLoading = inject("formLoading")
|
const formLoading = inject("formLoading")
|
||||||
const getColumnService = inject("getColumnService")
|
const getColumnService = inject("getColumnService")
|
||||||
const columns = inject("columns")
|
const columns = inject("columns")
|
||||||
const rv = async (ev, value = undefined) =>
|
const rv = async (ev, value = undefined) => await runEvent(props.component, ev, { formModel, getColumnService, columns }, value)
|
||||||
await runEvent(props.component, ev, { formModel, getColumnService, columns }, value)
|
|
||||||
|
|
||||||
const index = props.customField ?? props.component.dataIndex
|
const index = props.customField ?? props.component.dataIndex
|
||||||
const dictIndex = index.match(/^(\w+\.)\d+\./)
|
const dictIndex = index.match(/^(\w+\.)\d+\./) ? index.match(/^(\w+\.)\d+\./)[1] + props.component.dataIndex : props.component.dataIndex
|
||||||
? index.match(/^(\w+\.)\d+\./)[1] + props.component.dataIndex
|
|
||||||
: props.component.dataIndex
|
|
||||||
const value = ref(get(formModel.value, index, ""))
|
const value = ref(get(formModel.value, index, ""))
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -65,11 +58,7 @@ watch(
|
|||||||
|
|
||||||
if (value.value === "") {
|
if (value.value === "") {
|
||||||
value.value = undefined
|
value.value = undefined
|
||||||
} else if (
|
} else if (!isUndefined(value.value) && props.component.dict && (props.component.dict.name || props.component.dict.data)) {
|
||||||
!isUndefined(value.value) &&
|
|
||||||
props.component.dict &&
|
|
||||||
(props.component.dict.name || props.component.dict.data)
|
|
||||||
) {
|
|
||||||
value.value = value.value + ""
|
value.value = value.value + ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full" ref="containerRef" id="form-main-id">
|
||||||
<a-spin :loading="formLoading" :tip="options.loadingText" class="w-full ma-form-spin">
|
<a-spin :loading="formLoading" :tip="options.loadingText" class="w-full ma-form-spin">
|
||||||
<div
|
<div v-if="options.showFormTitle" :class="['ma-form-title', options.formTitleClass]" :style="options.formTitleStyle">
|
||||||
v-if="options.showFormTitle"
|
|
||||||
:class="['ma-form-title', options.formTitleClass]"
|
|
||||||
:style="options.formTitleStyle"
|
|
||||||
>
|
|
||||||
{{ options.formTitle }}
|
{{ options.formTitle }}
|
||||||
</div>
|
</div>
|
||||||
<a-form
|
<a-form
|
||||||
@@ -37,23 +33,13 @@
|
|||||||
<a-space>
|
<a-space>
|
||||||
<slot name="formBeforeButtons" />
|
<slot name="formBeforeButtons" />
|
||||||
<slot name="formButtons">
|
<slot name="formButtons">
|
||||||
<a-button
|
<a-button :type="options.submitType" :status="options.submitStatus" v-if="options.submitShowBtn" html-type="submit">
|
||||||
:type="options.submitType"
|
|
||||||
:status="options.submitStatus"
|
|
||||||
v-if="options.submitShowBtn"
|
|
||||||
html-type="submit"
|
|
||||||
>
|
|
||||||
<template #icon v-if="options?.submitIcon">
|
<template #icon v-if="options?.submitIcon">
|
||||||
<component :is="options.submitIcon" />
|
<component :is="options.submitIcon" />
|
||||||
</template>
|
</template>
|
||||||
{{ options.submitText }}
|
{{ options.submitText }}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button
|
<a-button :type="options.resetType" :status="options.resetStatus" v-if="options.resetShowBtn" @click="resetForm">
|
||||||
:type="options.resetType"
|
|
||||||
:status="options.resetStatus"
|
|
||||||
v-if="options.resetShowBtn"
|
|
||||||
@click="resetForm"
|
|
||||||
>
|
|
||||||
<template #icon v-if="options?.resetIcon">
|
<template #icon v-if="options?.resetIcon">
|
||||||
<component :is="options.resetIcon" />
|
<component :is="options.resetIcon" />
|
||||||
</template>
|
</template>
|
||||||
@@ -69,28 +55,36 @@
|
|||||||
<template v-if="parentKey">
|
<template v-if="parentKey">
|
||||||
<ParentPreview :parent-key="parentKey"></ParentPreview>
|
<ParentPreview :parent-key="parentKey"></ParentPreview>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- ctrl+F的替换功能:仅支持用例、测试项 -->
|
||||||
|
<template v-if="form.testStep || form.testContent">
|
||||||
|
<teleport to="body">
|
||||||
|
<div class="alert-container">
|
||||||
|
<a-alert show-icon type="info">
|
||||||
|
<p>支持</p>
|
||||||
|
<p>Ctrl+F</p>
|
||||||
|
<p>替换</p>
|
||||||
|
</a-alert>
|
||||||
|
</div>
|
||||||
|
</teleport>
|
||||||
|
<BulkReplaceModal ref="replaceModalRef" :parent-form="form" />
|
||||||
|
</template>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, provide, onMounted, nextTick, getCurrentInstance, inject, computed } from "vue"
|
import { ref, watch, provide, onMounted, onUnmounted, nextTick, getCurrentInstance, inject, computed } from "vue"
|
||||||
import { isNil, set, get, cloneDeep } from "lodash-es"
|
import { isNil, set, get, cloneDeep } from "lodash-es"
|
||||||
import defaultOptions from "./js/defaultOptions.js"
|
import defaultOptions from "./js/defaultOptions.js"
|
||||||
import {
|
import { getComponentName, toHump, interactiveControl, handleFlatteningColumns, insertGlobalCssToHead, insertGlobalFunctionsToHtml } from "./js/utils.js"
|
||||||
getComponentName,
|
|
||||||
toHump,
|
|
||||||
interactiveControl,
|
|
||||||
handleFlatteningColumns,
|
|
||||||
insertGlobalCssToHead,
|
|
||||||
insertGlobalFunctionsToHtml
|
|
||||||
} from "./js/utils.js"
|
|
||||||
import { loadDict, handlerCascader } from "./js/networkRequest.js"
|
import { loadDict, handlerCascader } from "./js/networkRequest.js"
|
||||||
import arrayComponentDefault from "./js/defaultArrayComponent.js"
|
import arrayComponentDefault from "./js/defaultArrayComponent.js"
|
||||||
import ColumnService from "./js/columnService.js"
|
import ColumnService from "./js/columnService.js"
|
||||||
|
|
||||||
import { runEvent } from "./js/event.js"
|
import { runEvent } from "./js/event.js"
|
||||||
import { Message } from "@arco-design/web-vue"
|
import { Message } from "@arco-design/web-vue"
|
||||||
|
// 修改源码导入:查找和替换组件
|
||||||
|
import BulkReplaceModal from "@/components/ma-form/Customs/BulkReplaceModal.vue"
|
||||||
|
|
||||||
const formLoading = ref(false)
|
const formLoading = ref(false)
|
||||||
const maFormRef = ref()
|
const maFormRef = ref()
|
||||||
@@ -100,7 +94,23 @@ const dictList = ref({})
|
|||||||
const cascaderList = ref([])
|
const cascaderList = ref([])
|
||||||
const form = ref({})
|
const form = ref({})
|
||||||
|
|
||||||
// ~~~custom start - 新增功能利用key强制更新form表单组件
|
// ~~~custom start0 - 查找和替换组件
|
||||||
|
const replaceModalRef = ref()
|
||||||
|
const containerRef = ref()
|
||||||
|
const handleKeydown = (e) => {
|
||||||
|
if (e.ctrlKey && e.key === "f") {
|
||||||
|
e.preventDefault()
|
||||||
|
replaceModalRef.value && replaceModalRef.value.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener("keydown", handleKeydown)
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener("keydown", handleKeydown)
|
||||||
|
})
|
||||||
|
|
||||||
|
// ~~~custom start1 - 新增功能利用key强制更新form表单组件
|
||||||
const componentKey = ref(0)
|
const componentKey = ref(0)
|
||||||
const updateKey = () => {
|
const updateKey = () => {
|
||||||
componentKey.value += 1
|
componentKey.value += 1
|
||||||
@@ -113,15 +123,15 @@ const handleChangeDisplay = (type) => {
|
|||||||
}
|
}
|
||||||
// ~~~custom end
|
// ~~~custom end
|
||||||
|
|
||||||
// ~~~~custom start
|
// ~~~~custom start2
|
||||||
// 2025年5月14日新增功能hover查看上级节点
|
// 2025年5月14日新增功能hover查看上级节点
|
||||||
import ParentPreview from "@/views/project/ParentPreview/index.vue"
|
import ParentPreview from "@/views/project/ParentPreview/index.vue"
|
||||||
// 判断是否有
|
// 判断是否有
|
||||||
const formKey = computed(() => {
|
const formKey = computed(() => {
|
||||||
// 去掉双击被测件:即key.split("").length > 1
|
// 去掉双击被测件:即key.split("").length > 1
|
||||||
if (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 ""
|
return ""
|
||||||
})
|
})
|
||||||
@@ -221,14 +231,7 @@ const init = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 联动
|
// 联动
|
||||||
await handlerCascader(
|
await handlerCascader(get(form.value, item.dataIndex), item, flatteningColumns.value, dictList.value, form.value, false)
|
||||||
get(form.value, item.dataIndex),
|
|
||||||
item,
|
|
||||||
flatteningColumns.value,
|
|
||||||
dictList.value,
|
|
||||||
form.value,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await nextTick(() => {
|
await nextTick(() => {
|
||||||
@@ -326,8 +329,21 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
div:deep(.arco-modal-container) {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
:deep(.arco-modal.arco-modal-draggable) {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
.ma-form-title {
|
.ma-form-title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.alert-container {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 50%;
|
||||||
|
user-select: none;
|
||||||
|
width: 113px;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
<!--
|
|
||||||
- @Author XXX
|
|
||||||
- @Link XXX
|
|
||||||
-->
|
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<a-input-group class="w-full">
|
<a-input-group class="w-full">
|
||||||
|
|||||||
@@ -11,11 +11,7 @@
|
|||||||
:label-style="props.labelStyle"
|
:label-style="props.labelStyle"
|
||||||
:value-style="props.valueStyle"
|
:value-style="props.valueStyle"
|
||||||
>
|
>
|
||||||
<a-descriptions-item
|
<a-descriptions-item v-for="item in descriptions" :label="item.label" :span="item.span ? item.span : isArray(item.value) ? props.column : 1">
|
||||||
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'">
|
<template v-if="item.formType === 'upload'">
|
||||||
<a-image-preview-group infinite v-if="isArray(item.value)">
|
<a-image-preview-group infinite v-if="isArray(item.value)">
|
||||||
<a-space>
|
<a-space>
|
||||||
@@ -34,11 +30,10 @@
|
|||||||
<template v-for="(sub, idx) in item.value" :key="idx">
|
<template v-for="(sub, idx) in item.value" :key="idx">
|
||||||
<!-- 这是每个测试子项 -->
|
<!-- 这是每个测试子项 -->
|
||||||
<div class="subTitle mt-1">{{ idx + 1 }}.{{ sub.subName }}</div>
|
<div class="subTitle mt-1">{{ idx + 1 }}.{{ sub.subName }}</div>
|
||||||
|
<div>测试子项描述:{{ sub.subDescription }}</div>
|
||||||
<template v-for="(step, index) in sub.subStep" :key="index">
|
<template v-for="(step, index) in sub.subStep" :key="index">
|
||||||
<span class="text-amber-700">步骤{{ index + 1 }})</span>
|
<span class="text-amber-700">步骤{{ index + 1 }})</span>
|
||||||
<div class="operation">
|
<div class="operation"><span class="text-bold">操作:</span>{{ step.operation }}</div>
|
||||||
<span class="text-bold">操作:</span>{{ step.operation }}
|
|
||||||
</div>
|
|
||||||
<div class="mb-1"><span class="text-bold">预期:</span>{{ step.expect }}</div>
|
<div class="mb-1"><span class="text-bold">预期:</span>{{ step.expect }}</div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -46,9 +41,7 @@
|
|||||||
<template v-else>暂无信息</template>
|
<template v-else>暂无信息</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template
|
<template v-else-if="item.formType === 'radio' || item.formType === 'select' || item.formType === 'checkbox'">
|
||||||
v-else-if="item.formType === 'radio' || item.formType === 'select' || item.formType === 'checkbox'"
|
|
||||||
>
|
|
||||||
<template v-if="isArray(item.value)">
|
<template v-if="isArray(item.value)">
|
||||||
<!-- 修改源码 -->
|
<!-- 修改源码 -->
|
||||||
<a-space>
|
<a-space>
|
||||||
@@ -162,6 +155,8 @@ defineExpose({ reset })
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.white-space-change {
|
.white-space-change {
|
||||||
|
max-height: 68vh;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,18 +5,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col w-full h-full">
|
<div class="flex flex-col w-full h-full">
|
||||||
<a-input-group class="mb-2 w-full" size="mini">
|
<a-input-group class="mb-2 w-full" size="mini">
|
||||||
<a-input
|
<a-input :placeholder="props?.searchPlaceholder" allow-clear @input="changeKeyword" @clear="resetData" />
|
||||||
:placeholder="props?.searchPlaceholder"
|
|
||||||
allow-clear
|
|
||||||
@input="changeKeyword"
|
|
||||||
@clear="resetData"
|
|
||||||
/>
|
|
||||||
<a-button
|
<a-button
|
||||||
@click="() => {
|
@click="
|
||||||
|
() => {
|
||||||
isExpand ? maTree.expandAll(false) : maTree.expandAll(true)
|
isExpand ? maTree.expandAll(false) : maTree.expandAll(true)
|
||||||
isExpand = ! isExpand
|
isExpand = !isExpand
|
||||||
}"
|
}
|
||||||
>{{ isExpand ? '折叠' : '展开' }}</a-button>
|
"
|
||||||
|
>{{ isExpand ? "折叠" : "展开" }}</a-button
|
||||||
|
>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
<a-tree
|
<a-tree
|
||||||
blockNode
|
blockNode
|
||||||
@@ -34,62 +32,67 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, computed } from 'vue'
|
import { ref, watch, computed } from "vue"
|
||||||
|
|
||||||
const treeData = ref([])
|
const treeData = ref([])
|
||||||
const maTree = ref()
|
const maTree = ref()
|
||||||
const isExpand = ref(false)
|
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 },
|
modelValue: { type: Array },
|
||||||
data: { type: Array },
|
data: { type: Array },
|
||||||
searchPlaceholder: { type: String },
|
searchPlaceholder: { type: String },
|
||||||
fieldNames: { type: Object, default: () => { return { title: 'label', key: 'value' } } },
|
fieldNames: {
|
||||||
icon: { type: String, default: undefined },
|
type: Object,
|
||||||
})
|
default: () => {
|
||||||
|
return { title: "label", key: "value" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: { type: String, default: undefined }
|
||||||
|
})
|
||||||
|
|
||||||
const modelValue = computed({
|
const modelValue = computed({
|
||||||
get() {
|
get() {
|
||||||
return props.modelValue
|
return props.modelValue
|
||||||
},
|
},
|
||||||
set(newVal) {
|
set(newVal) {
|
||||||
emit('update:modelValue', newVal)
|
emit("update:modelValue", newVal)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.data,
|
() => props.data,
|
||||||
val => {
|
(val) => {
|
||||||
treeData.value = val
|
treeData.value = val
|
||||||
},
|
},
|
||||||
{ immediate: true, deep: true }
|
{ immediate: true, deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const handlerSelect = (item, data) => {
|
const handlerSelect = (item, data) => {
|
||||||
modelValue.value = [ item ]
|
modelValue.value = [item]
|
||||||
emit('click', ...[item, data])
|
emit("click", ...[item, data])
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetData = () => treeData.value = props.data
|
const resetData = () => (treeData.value = props.data)
|
||||||
|
|
||||||
const changeKeyword = (keyword) => {
|
const changeKeyword = (keyword) => {
|
||||||
if (!keyword || keyword === '') {
|
if (!keyword || keyword === "") {
|
||||||
treeData.value = Object.assign(props.data, [])
|
treeData.value = Object.assign(props.data, [])
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
treeData.value = searchNode(keyword)
|
treeData.value = searchNode(keyword)
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchNode = (keyword) => {
|
const searchNode = (keyword) => {
|
||||||
const loop = (data) => {
|
const loop = (data) => {
|
||||||
let tree = []
|
let tree = []
|
||||||
data.map(item => {
|
data.map((item) => {
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
const temp = loop(item.children)
|
const temp = loop(item.children)
|
||||||
tree.push(...temp)
|
tree.push(...temp)
|
||||||
} else if (item[props.fieldNames['title']].indexOf(keyword) !== -1) {
|
} else if (item[props.fieldNames["title"]].indexOf(keyword) !== -1) {
|
||||||
tree.push(item)
|
tree.push(item)
|
||||||
}
|
}
|
||||||
return tree
|
return tree
|
||||||
@@ -98,9 +101,9 @@
|
|||||||
return tree
|
return tree
|
||||||
}
|
}
|
||||||
return loop(treeData.value)
|
return loop(treeData.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ maTree })
|
defineExpose({ maTree })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
/>
|
/>
|
||||||
</a-space>
|
</a-space>
|
||||||
|
|
||||||
<a-modal v-model:visible="visible" width="1000px" draggable :on-before-ok="close" unmountOnClose>
|
<a-modal v-model:visible="visible" width="1000px" draggable :on-before-ok="close" unmount-on-close>
|
||||||
<template #title>{{ props.text }}</template>
|
<template #title>{{ props.text }}</template>
|
||||||
|
|
||||||
<ma-crud
|
<ma-crud
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ interface ITextInfo {
|
|||||||
const textInfo: ITextInfo = {
|
const textInfo: ITextInfo = {
|
||||||
testUnitAddDefaultText: "中国科学院卫星软件测评中心",
|
testUnitAddDefaultText: "中国科学院卫星软件测评中心",
|
||||||
testUnitContactPersonName: "高才栋",
|
testUnitContactPersonName: "高才栋",
|
||||||
testUnintContactPhoneNumber: "13564753024"
|
testUnintContactPhoneNumber: "010-50735018"
|
||||||
}
|
}
|
||||||
export default textInfo
|
export default textInfo
|
||||||
|
|||||||
@@ -12,13 +12,17 @@ export default function () {
|
|||||||
const tempCaseInfo = ref<any>(null)
|
const tempCaseInfo = ref<any>(null)
|
||||||
// 项目id和当前case的key
|
// 项目id和当前case的key
|
||||||
const { id, key } = route.query
|
const { id, key } = route.query
|
||||||
onMounted(async () => {
|
const fetchCaseOneStatus = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await caseApi.getCaseOne({ key, projectId: id })
|
const res = await caseApi.getCaseOne({ key, projectId: id })
|
||||||
tempCaseInfo.value = res.data
|
tempCaseInfo.value = res.data
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Message.error("获取用例信息失败,请检查服务器")
|
Message.error("获取用例信息失败,请检查服务器")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 在初次加载时更新状态,如何在用例更新后再次加载呢
|
||||||
|
onMounted(() => {
|
||||||
|
fetchCaseOneStatus()
|
||||||
})
|
})
|
||||||
// hook里面判断函数:判断是否该用例未执行或未通过
|
// hook里面判断函数:判断是否该用例未执行或未通过
|
||||||
const caseIsNotPassedOrNotExe = function (): boolean {
|
const caseIsNotPassedOrNotExe = function (): boolean {
|
||||||
@@ -34,7 +38,8 @@ export default function () {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
tempCaseInfo,
|
tempCaseInfo,
|
||||||
caseIsNotPassedOrNotExe
|
caseIsNotPassedOrNotExe,
|
||||||
|
fetchCaseOneStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
:heading="5"
|
:heading="5"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="font-extrabold bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-purple-600"
|
class="font-extrabold bg-clip-text text-transparent bg-linear-to-r from-blue-500 to-purple-600"
|
||||||
>
|
>
|
||||||
测试管理平台
|
测试管理平台
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,69 +48,30 @@
|
|||||||
<template v-if="nodeData.level === '0'">
|
<template v-if="nodeData.level === '0'">
|
||||||
<a-tooltip content="点击新增轮次">
|
<a-tooltip content="点击新增轮次">
|
||||||
<IconPlus
|
<IconPlus
|
||||||
style="
|
style="position: absolute; right: 8px; font-size: 12px; top: 8px; color: #3370ff"
|
||||||
position: absolute;
|
|
||||||
right: 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
top: 8px;
|
|
||||||
color: #3370ff;
|
|
||||||
"
|
|
||||||
@click="() => handleRoundAddClick(nodeData)"
|
@click="() => handleRoundAddClick(nodeData)"
|
||||||
/>
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip content="点击删除轮次">
|
<a-tooltip content="点击删除轮次">
|
||||||
<a-popconfirm
|
<a-popconfirm content="确定要删除该轮次吗?" position="bottom" @ok="handleRoundDelClick(nodeData)">
|
||||||
content="确定要删除该轮次吗?"
|
<icon-close style="position: absolute; right: 25px; font-size: 12px; top: 8px; color: #3370ff" />
|
||||||
position="bottom"
|
|
||||||
@ok="handleRoundDelClick(nodeData)"
|
|
||||||
>
|
|
||||||
<IconMinus
|
|
||||||
style="
|
|
||||||
position: absolute;
|
|
||||||
right: 25px;
|
|
||||||
font-size: 12px;
|
|
||||||
top: 8px;
|
|
||||||
color: #3370ff;
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip content="点击编辑当前轮次"
|
<a-tooltip content="点击编辑当前轮次"
|
||||||
><IconEdit
|
><IconEdit
|
||||||
style="
|
style="position: absolute; right: 42px; font-size: 12px; top: 8px; color: #3370ff"
|
||||||
position: absolute;
|
|
||||||
right: 42px;
|
|
||||||
font-size: 12px;
|
|
||||||
top: 8px;
|
|
||||||
color: #3370ff;
|
|
||||||
"
|
|
||||||
@click="() => handleRoundEditClick(nodeData)"
|
@click="() => handleRoundEditClick(nodeData)"
|
||||||
/></a-tooltip>
|
/></a-tooltip>
|
||||||
<a-tooltip content="综合阅览该轮次下问题单"
|
<a-tooltip content="综合阅览该轮次下问题单"
|
||||||
><icon-bug
|
><icon-bug
|
||||||
style="
|
style="position: absolute; right: 60px; font-size: 12px; top: 8px; color: #3370ff"
|
||||||
position: absolute;
|
|
||||||
right: 60px;
|
|
||||||
font-size: 12px;
|
|
||||||
top: 8px;
|
|
||||||
color: #3370ff;
|
|
||||||
"
|
|
||||||
@click="handleProblemShowClick"
|
@click="handleProblemShowClick"
|
||||||
/></a-tooltip>
|
/></a-tooltip>
|
||||||
<a-tooltip content="轮次数据管理视图"
|
<a-tooltip content="轮次数据管理视图"
|
||||||
><icon-storage
|
><icon-storage
|
||||||
style="
|
style="position: absolute; right: 77px; font-size: 12px; top: 8px; color: #3370ff"
|
||||||
position: absolute;
|
|
||||||
right: 77px;
|
|
||||||
font-size: 12px;
|
|
||||||
top: 8px;
|
|
||||||
color: #3370ff;
|
|
||||||
"
|
|
||||||
:style="{
|
:style="{
|
||||||
color:
|
color: isOpeSetsRoute && nodeData.key === route.query.key ? 'red' : '#3370ff'
|
||||||
isOpeSetsRoute && nodeData.key === route.query.key
|
|
||||||
? 'red'
|
|
||||||
: '#3370ff'
|
|
||||||
}"
|
}"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
@@ -127,13 +88,15 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<!-- 设计节点的图标 -->
|
<!-- 设计节点的图标 -->
|
||||||
<template #switcher-icon="node, { isLeaf }">
|
<template #switcher-icon="_, { isLeaf }">
|
||||||
<IconDown v-if="!isLeaf" />
|
<div class="font-icon">
|
||||||
|
<IconDownCircle v-if="!isLeaf" />
|
||||||
<IconFile v-if="isLeaf" />
|
<IconFile v-if="isLeaf" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- 节点图标插槽 -->
|
<!-- 节点图标插槽 -->
|
||||||
<template #icon="props">
|
<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 === '2'"> [设] </template>
|
||||||
<template v-if="props.node.level === '3'"> [项] </template>
|
<template v-if="props.node.level === '3'"> [项] </template>
|
||||||
<template v-if="props.node.level === '4'">
|
<template v-if="props.node.level === '4'">
|
||||||
@@ -156,6 +119,16 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 节点title插槽 -->
|
||||||
|
<template #title="{ level, title }">
|
||||||
|
<template v-if="level === '0' || level === '3'">
|
||||||
|
{{ title }}
|
||||||
|
<span class="small-right-context-tip">
|
||||||
|
<i>右键</i>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>{{ title }}</template>
|
||||||
|
</template>
|
||||||
</a-tree>
|
</a-tree>
|
||||||
</div>
|
</div>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
@@ -177,15 +150,7 @@
|
|||||||
</a-button>
|
</a-button>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</a-back-top>
|
</a-back-top>
|
||||||
<ma-form-modal
|
<ma-form-modal ref="maFormModalRef" :title="title" :column="roundColumn" :options="roundOption" :width="800" :submit="handleRoundSubmit"> </ma-form-modal>
|
||||||
ref="maFormModalRef"
|
|
||||||
:title="title"
|
|
||||||
:column="roundColumn"
|
|
||||||
:options="roundOption"
|
|
||||||
:width="800"
|
|
||||||
:submit="handleRoundSubmit"
|
|
||||||
>
|
|
||||||
</ma-form-modal>
|
|
||||||
<!-- 如果没有第一轮的SO_dut则弹窗 -->
|
<!-- 如果没有第一轮的SO_dut则弹窗 -->
|
||||||
<ma-form-modal
|
<ma-form-modal
|
||||||
ref="soDutFormRef"
|
ref="soDutFormRef"
|
||||||
@@ -202,20 +167,9 @@
|
|||||||
<!-- 添加input前缀 -->
|
<!-- 添加input前缀 -->
|
||||||
<template #inputPrepend-version> V </template>
|
<template #inputPrepend-version> V </template>
|
||||||
</ma-form-modal>
|
</ma-form-modal>
|
||||||
<Progress
|
<Progress :visible="visible" :isComplete="isComplete" :text="ptext" @clickConfirm="handleModalConfirmClick"></Progress>
|
||||||
:visible="visible"
|
|
||||||
:isComplete="isComplete"
|
|
||||||
:text="ptext"
|
|
||||||
@clickConfirm="handleModalConfirmClick"
|
|
||||||
></Progress>
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<a-dropdown
|
<a-dropdown v-model:popup-visible="popupVisible" :popup-container="popupContainer" position="bottom" alignPoint :style="{ display: 'block' }">
|
||||||
v-model:popup-visible="popupVisible"
|
|
||||||
:popup-container="popupContainer"
|
|
||||||
position="bottom"
|
|
||||||
alignPoint
|
|
||||||
:style="{ display: 'block' }"
|
|
||||||
>
|
|
||||||
<template #content>
|
<template #content>
|
||||||
<a-dgroup title="执行操作">
|
<a-dgroup title="执行操作">
|
||||||
<a-doption @click="handleDoptionClickGreateCases">
|
<a-doption @click="handleDoptionClickGreateCases">
|
||||||
@@ -237,7 +191,7 @@
|
|||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
<!-- 复制modal组件 -->
|
<!-- 复制modal组件 -->
|
||||||
<!-- 关联的modal组件 -->
|
<!-- 关联的modal组件 -->
|
||||||
<a-modal v-model:visible="modalVisible" :width="700" draggable :on-before-ok="handleCopyDemand">
|
<a-modal v-model:visible="modalVisible" unmount-on-close :width="700" draggable :on-before-ok="handleCopyDemand">
|
||||||
<template #title>复制到设计需求</template>
|
<template #title>复制到设计需求</template>
|
||||||
<div class="pb-3">选择复制到的节点(<span class="point">支持搜索</span>):</div>
|
<div class="pb-3">选择复制到的节点(<span class="point">支持搜索</span>):</div>
|
||||||
<a-cascader
|
<a-cascader
|
||||||
@@ -278,6 +232,7 @@
|
|||||||
@update:visible="roundRightVisible = false"
|
@update:visible="roundRightVisible = false"
|
||||||
:container="roundRightContainer"
|
:container="roundRightContainer"
|
||||||
@click-problem-show="handleProblemShowClick"
|
@click-problem-show="handleProblemShowClick"
|
||||||
|
@create-renji="handleCreateRenji"
|
||||||
></roundRight>
|
></roundRight>
|
||||||
<!-- w2:轮次的问题单ma-crud,这里要传参2个,首先是请求另外一个接口,然后取消是否关联字段 -->
|
<!-- w2:轮次的问题单ma-crud,这里要传参2个,首先是请求另外一个接口,然后取消是否关联字段 -->
|
||||||
<problem-choose ref="problemRoundRef" hasRelated="roundProblem" :title="problemTitle"></problem-choose>
|
<problem-choose ref="problemRoundRef" hasRelated="roundProblem" :title="problemTitle"></problem-choose>
|
||||||
@@ -340,21 +295,13 @@ provide("toggleDrawerMenu", () => {
|
|||||||
const { searchKey, handleSearchTreeDataClick } = useSearchNodes()
|
const { searchKey, handleSearchTreeDataClick } = useSearchNodes()
|
||||||
|
|
||||||
//~~~~~~M功能:强制必须有源代码被测件~~~~~~
|
//~~~~~~M功能:强制必须有源代码被测件~~~~~~
|
||||||
const {
|
const { soDutFormRef, soDutModalTitle, handleSoDutExistsForceModal, handleSoDutSubmit, handleSoDutCancel, soDutColumn } = useMustSoDut(projectId)
|
||||||
soDutFormRef,
|
|
||||||
soDutModalTitle,
|
|
||||||
handleSoDutExistsForceModal,
|
|
||||||
handleSoDutSubmit,
|
|
||||||
handleSoDutCancel,
|
|
||||||
soDutColumn
|
|
||||||
} = useMustSoDut(projectId)
|
|
||||||
|
|
||||||
//~~~~~~小功能:展开和收缩~~~~~~
|
//~~~~~~小功能:展开和收缩~~~~~~
|
||||||
const { expandedKeys, toggleExpanded } = useNodeExpand()
|
const { expandedKeys, toggleExpanded } = useNodeExpand()
|
||||||
|
|
||||||
//~~~~~~大功能:单击/双击节点逻辑~~~~~~
|
//~~~~~~大功能:单击/双击节点逻辑~~~~~~
|
||||||
const { selectedKeys, pointNode, dutSubFormRef, designSubFormRef, testDemandSubFormRef, caseSubFormRef } =
|
const { selectedKeys, pointNode, dutSubFormRef, designSubFormRef, testDemandSubFormRef, caseSubFormRef } = useNodeClick(expandedKeys)
|
||||||
useNodeClick(expandedKeys)
|
|
||||||
|
|
||||||
//~~~~~~小功能:路由中key绑定selectedKeys:好像有点非性能~~~~~~
|
//~~~~~~小功能:路由中key绑定selectedKeys:好像有点非性能~~~~~~
|
||||||
const isOpeSetsRoute = ref(false)
|
const isOpeSetsRoute = ref(false)
|
||||||
@@ -375,16 +322,10 @@ watch(
|
|||||||
const { loadMore } = useLoadTreeNode()
|
const { loadMore } = useLoadTreeNode()
|
||||||
|
|
||||||
//~~~~~~功能:轮次的Ma-Form~~~~~~
|
//~~~~~~功能:轮次的Ma-Form~~~~~~
|
||||||
const {
|
const { maFormModalRef, title, roundColumn, roundOption, handleRoundAddClick, handleRoundEditClick, handleRoundDelClick, handleRoundSubmit } = useRoundMaForm(
|
||||||
maFormModalRef,
|
projectId,
|
||||||
title,
|
handleSoDutExistsForceModal
|
||||||
roundColumn,
|
)
|
||||||
roundOption,
|
|
||||||
handleRoundAddClick,
|
|
||||||
handleRoundEditClick,
|
|
||||||
handleRoundDelClick,
|
|
||||||
handleRoundSubmit
|
|
||||||
} = useRoundMaForm(projectId, handleSoDutExistsForceModal)
|
|
||||||
|
|
||||||
// 大功能:~~~~~~右键菜单实现~~~~~~
|
// 大功能:~~~~~~右键菜单实现~~~~~~
|
||||||
const {
|
const {
|
||||||
@@ -403,22 +344,12 @@ const {
|
|||||||
handleProblemShowClick,
|
handleProblemShowClick,
|
||||||
handleDoptionClickGreateCases,
|
handleDoptionClickGreateCases,
|
||||||
handleDoptionClickCopyDemand,
|
handleDoptionClickCopyDemand,
|
||||||
handleCopyDemand
|
handleCopyDemand,
|
||||||
|
handleCreateRenji
|
||||||
} = useRightClick(projectId, routeViewRef)
|
} = useRightClick(projectId, routeViewRef)
|
||||||
|
|
||||||
// ~~~~~~~~大功能:拖拽~~~~~~~~
|
// ~~~~~~~~大功能:拖拽~~~~~~~~
|
||||||
const {
|
const { paoVisible, paoContainer, pao2Visible, pao2Container, ondrop, allowdrop, paoOk, paoCancel, paoOk2, paoCancel2 } = useTreeDrag(projectId, routeViewRef)
|
||||||
paoVisible,
|
|
||||||
paoContainer,
|
|
||||||
pao2Visible,
|
|
||||||
pao2Container,
|
|
||||||
ondrop,
|
|
||||||
allowdrop,
|
|
||||||
paoOk,
|
|
||||||
paoCancel,
|
|
||||||
paoOk2,
|
|
||||||
paoCancel2
|
|
||||||
} = useTreeDrag(projectId, routeViewRef)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@@ -502,7 +433,39 @@ const {
|
|||||||
// 自定义选中节点样式
|
// 自定义选中节点样式
|
||||||
:deep(.arco-tree-node-selected) {
|
:deep(.arco-tree-node-selected) {
|
||||||
.arco-tree-node-title {
|
.arco-tree-node-title {
|
||||||
color: #F53F3F !important;
|
color: #f53f3f !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-bottom: -2.8px;
|
||||||
|
}
|
||||||
|
.font-icon:hover {
|
||||||
|
color: #f53f3fed;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
// 有右键菜单节点显示图标
|
||||||
|
.small-right-context-tip {
|
||||||
|
align-items: center;
|
||||||
|
background: linear-gradient(54.58deg, rgba(74, 228, 255, 0.12) -14.12%, rgba(66, 130, 255, 0.12) 47.61%, rgba(215, 104, 255, 0.12) 105.84%);
|
||||||
|
border: 1px solid #d5d7f9;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: transparent;
|
||||||
|
display: inline-flex;
|
||||||
|
height: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 2px;
|
||||||
|
padding: 0 2.5px;
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
i {
|
||||||
|
-webkit-text-fill-color: transparent !important;
|
||||||
|
zoom: 0.7;
|
||||||
|
background: linear-gradient(85.2deg, #0062ff -3.15%, #cb50ff 98.89%) !important;
|
||||||
|
background-clip: text !important;
|
||||||
|
-webkit-background-clip: text !important;
|
||||||
|
font-size: 10px;
|
||||||
|
font-style: normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -26,6 +26,12 @@
|
|||||||
</template>
|
</template>
|
||||||
查看轮次下问题单
|
查看轮次下问题单
|
||||||
</a-doption>
|
</a-doption>
|
||||||
|
<a-doption @click="handleRenjiTest">
|
||||||
|
<template #icon>
|
||||||
|
<icon-file />
|
||||||
|
</template>
|
||||||
|
点击生成人机交互界面测试
|
||||||
|
</a-doption>
|
||||||
</a-dgroup>
|
</a-dgroup>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown>
|
</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) => {
|
const handleSelect = async (value) => {
|
||||||
emits("update:visible") // 给父组件传递关闭弹窗
|
emits("update:visible") // 给父组件传递关闭弹窗
|
||||||
@@ -60,6 +66,10 @@ const change = () => {
|
|||||||
const handleProblemShow = () => {
|
const handleProblemShow = () => {
|
||||||
emits("click-problem-show")
|
emits("click-problem-show")
|
||||||
}
|
}
|
||||||
|
// 点击生成人机交互界面测试
|
||||||
|
const handleRenjiTest = () => {
|
||||||
|
emits("create-renji")
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped></style>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import caseApi from "@/api/project/case"
|
|||||||
import designApi from "@/api/project/designDemand"
|
import designApi from "@/api/project/designDemand"
|
||||||
import demandApi from "@/api/project/testDemand"
|
import demandApi from "@/api/project/testDemand"
|
||||||
import { useTreeDataStore } from "@/store"
|
import { useTreeDataStore } from "@/store"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 右键测试项节点与轮次的hook
|
* 右键测试项节点与轮次的hook
|
||||||
*/
|
*/
|
||||||
@@ -135,6 +136,19 @@ export function useRightClick(projectId, routeViewRef) {
|
|||||||
Message.error("复制失败,服务器错误")
|
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 {
|
return {
|
||||||
popupVisible,
|
popupVisible,
|
||||||
popupContainer,
|
popupContainer,
|
||||||
@@ -151,6 +165,7 @@ export function useRightClick(projectId, routeViewRef) {
|
|||||||
handleProblemShowClick,
|
handleProblemShowClick,
|
||||||
handleDoptionClickGreateCases,
|
handleDoptionClickGreateCases,
|
||||||
handleDoptionClickCopyDemand,
|
handleDoptionClickCopyDemand,
|
||||||
handleCopyDemand
|
handleCopyDemand,
|
||||||
|
handleCreateRenji
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ import { storeToRefs } from "pinia"
|
|||||||
import { Message, Notification } from "@arco-design/web-vue"
|
import { Message, Notification } from "@arco-design/web-vue"
|
||||||
import caseApi from "@/api/project/case"
|
import caseApi from "@/api/project/case"
|
||||||
import { useTreeDataStore } from "@/store"
|
import { useTreeDataStore } from "@/store"
|
||||||
|
import designApi from "@/api/project/designDemand"
|
||||||
|
import { useRoute } from "vue-router"
|
||||||
|
|
||||||
export default function useTreeDrag(projectId, routeViewRef) {
|
export default function useTreeDrag(projectId, routeViewRef) {
|
||||||
// global
|
// global
|
||||||
|
const route = useRoute()
|
||||||
const treeDataStore = useTreeDataStore()
|
const treeDataStore = useTreeDataStore()
|
||||||
const { treeData } = storeToRefs(treeDataStore)
|
const { treeData } = storeToRefs(treeDataStore)
|
||||||
// 闭包储存变量
|
// 闭包储存变量
|
||||||
@@ -19,15 +22,15 @@ export default function useTreeDrag(projectId, routeViewRef) {
|
|||||||
const pao2Container = ref(null)
|
const pao2Container = ref(null)
|
||||||
// a-tree -> 节点在可释放目标释放的操作 - drapNode是被拖拽的节点,
|
// a-tree -> 节点在可释放目标释放的操作 - drapNode是被拖拽的节点,
|
||||||
// dropNone是释放在哪个节点下,dropPosition是释放的位置-1,0...
|
// dropNone是释放在哪个节点下,dropPosition是释放的位置-1,0...
|
||||||
const ondrop = ({ e, dragNode, dropNode, dropPosition }) => {
|
const ondrop = async ({ e, dragNode, dropNode, dropPosition }) => {
|
||||||
const data = treeData.value // 1.这是整体的树数据
|
const data = treeData.value // 这是整体的树数据
|
||||||
// 提示用户只能拖拽用例节点
|
// 提示用户只能拖拽用例节点、设计需求节点
|
||||||
if (dragNode.level !== "4") {
|
if (!["2", "4"].includes(dragNode.level)) {
|
||||||
Message.warning("只能拖拽用例节点")
|
Message.warning("只能拖拽用例节点、设计需求节点")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 拖拽逻辑:
|
// 拖拽逻辑:
|
||||||
// 1.首先只能拖拽用例节点才能实现功能
|
// 1.用例节点拖拽
|
||||||
if (dragNode.level === "4") {
|
if (dragNode.level === "4") {
|
||||||
// 2.1.如果是拖拽到测试项节点下
|
// 2.1.如果是拖拽到测试项节点下
|
||||||
if (dropNode.level === "3") {
|
if (dropNode.level === "3") {
|
||||||
@@ -58,10 +61,19 @@ export default function useTreeDrag(projectId, routeViewRef) {
|
|||||||
dragDropPosition = dropPosition
|
dragDropPosition = dropPosition
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 2.设计需求节点拖拽-改变sort字段
|
||||||
|
if (dragNode.level === "2") {
|
||||||
|
// 如果位置为0,即放在节点上不处理
|
||||||
|
if (dropPosition === 0) return
|
||||||
|
// 如果不为0,则发出请求,传key后端处理即可
|
||||||
|
const res = await designApi.switch_position(dragNode.key, dropNode.key, dropPosition, route.query.id)
|
||||||
|
// 最后更新树状目录
|
||||||
|
treeDataStore.updateDesignDemandTreeData({ key: res.data }, route.query.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// a-tree拖拽时是否允许在某级别节点上释放:目前支持在测试项节点、测试用例节点释放
|
// a-tree拖拽时是否允许在某级别节点上释放:目前支持在测试项节点、测试用例节点释放
|
||||||
const allowdrop = (options) => {
|
const allowdrop = (options) => {
|
||||||
if (options.dropNode.level === "4" || options.dropNode.level === "3") {
|
if (["4", "3", "2"].includes(options.dropNode.level)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export default function useNodeClick(expandedKeys: Ref<string[]>) {
|
|||||||
timerId && clearTimeout(timerId)
|
timerId && clearTimeout(timerId)
|
||||||
timerId = null
|
timerId = null
|
||||||
}
|
}
|
||||||
}, 250)
|
}, 250) as any
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
selectedKeys,
|
selectedKeys,
|
||||||
|
|||||||
@@ -32,3 +32,11 @@ body {
|
|||||||
.arco-card-body {
|
.arco-card-body {
|
||||||
padding: 8px !important;
|
padding: 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 覆盖modal的头部样式,变为sticky
|
||||||
|
.arco-modal-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
@@ -91,4 +91,25 @@ tool.chnRoundNameArray = [
|
|||||||
"第十六轮"
|
"第十六轮"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 将html变为纯文本
|
||||||
|
tool.htmlToTextWithDOM = (htmlString) => {
|
||||||
|
// 1. 创建一个临时的div元素
|
||||||
|
const tempDiv = document.createElement("div")
|
||||||
|
// 2. 将HTML字符串设置为临时div的内容
|
||||||
|
tempDiv.innerHTML = htmlString
|
||||||
|
// 3. 使用innerText属性获取纯文本,这会自动忽略所有HTML标签
|
||||||
|
const text = tempDiv.innerText || tempDiv.textContent
|
||||||
|
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
|
export default tool
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<a-modal v-model:visible="visible" @ok="resetLogin">
|
<a-modal v-model:visible="visible" @ok="resetLogin" unmount-on-close>
|
||||||
<template #title>提示</template>
|
<template #title>提示</template>
|
||||||
密码已经修改成功,需要重新登录系统,点击确定跳转登录页面。
|
密码已经修改成功,需要重新登录系统,点击确定跳转登录页面。
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|||||||
@@ -23,11 +23,7 @@
|
|||||||
<a-tabs default-active-key="login-log" :animation="true">
|
<a-tabs default-active-key="login-log" :animation="true">
|
||||||
<a-tab-pane key="login-log" title="登录日志" v-loading="isDataLoading">
|
<a-tab-pane key="login-log" title="登录日志" v-loading="isDataLoading">
|
||||||
<a-timeline class="pl-5 mt-3" v-if="loginLogList.length">
|
<a-timeline class="pl-5 mt-3" v-if="loginLogList.length">
|
||||||
<a-timeline-item
|
<a-timeline-item :label="`IP地址:${item.ip},操作系统:${item.os}`" v-for="(item, idx) in loginLogList" :key="idx">
|
||||||
:label="`IP地址:${item.ip},操作系统:${item.os}`"
|
|
||||||
v-for="(item, idx) in loginLogList"
|
|
||||||
:key="idx"
|
|
||||||
>
|
|
||||||
您于 {{ item.create_datetime }} 登录系统,浏览器:{{ item.browser }}
|
您于 {{ item.create_datetime }} 登录系统,浏览器:{{ item.browser }}
|
||||||
</a-timeline-item>
|
</a-timeline-item>
|
||||||
</a-timeline>
|
</a-timeline>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<a-link @click="viewDetail(record)">{{ record.title }}</a-link>
|
<a-link @click="viewDetail(record)">{{ record.title }}</a-link>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
<a-modal v-model:visible="detailVisible" width="80%" draggable :footer="false">
|
<a-modal v-model:visible="detailVisible" width="80%" draggable :footer="false" unmount-on-close>
|
||||||
<template #title>公告详情</template>
|
<template #title>公告详情</template>
|
||||||
<a-typography :style="{ marginTop: '-30px' }">
|
<a-typography :style="{ marginTop: '-30px' }">
|
||||||
<a-typography-title class="text-center">
|
<a-typography-title class="text-center">
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import useOpenChangeModal from "./useOpenChangeModal"
|
|||||||
|
|
||||||
const currentRow = ref({ id: undefined, name: undefined }) // 当前选择的行
|
const currentRow = ref({ id: undefined, name: undefined }) // 当前选择的行
|
||||||
const { crudRef, crudOptions, columns } = useCrudRef(currentRow)
|
const { crudRef, crudOptions, columns } = useCrudRef(currentRow)
|
||||||
|
|
||||||
const { visible, changeSort, changeStatus, open } = useOpenChangeModal(crudRef, currentRow)
|
const { visible, changeSort, changeStatus, open } = useOpenChangeModal(crudRef, currentRow)
|
||||||
// 暴露自己的open方法
|
// 暴露自己的open方法
|
||||||
defineExpose({ open })
|
defineExpose({ open })
|
||||||
|
|||||||
@@ -10,10 +10,7 @@
|
|||||||
<img src="@/assets/img/wxwx-logo.svg" width="45" /><span>{{ $title }}</span>
|
<img src="@/assets/img/wxwx-logo.svg" width="45" /><span>{{ $title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="slogan flex justify-end">
|
<div class="slogan flex justify-end">
|
||||||
<span
|
<span class="font-extrabold bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-purple-600">---- 为测评服务,打造测评高地</span>
|
||||||
class="font-extrabold bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-purple-600"
|
|
||||||
>---- 为测评服务,打造测评高地</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -28,14 +25,7 @@
|
|||||||
{ maxLength: 30, message: '用户名不能超过30个字符' }
|
{ maxLength: 30, message: '用户名不能超过30个字符' }
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input v-model="form.username" class="w-full" size="large" placeholder="用户名" allow-clear :max-length="30">
|
||||||
v-model="form.username"
|
|
||||||
class="w-full"
|
|
||||||
size="large"
|
|
||||||
placeholder="用户名"
|
|
||||||
allow-clear
|
|
||||||
:max-length="30"
|
|
||||||
>
|
|
||||||
<template #prefix><icon-user /></template>
|
<template #prefix><icon-user /></template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -48,13 +38,7 @@
|
|||||||
{ maxLength: 30, message: '密码不超过30字符' }
|
{ maxLength: 30, message: '密码不超过30字符' }
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<a-input-password
|
<a-input-password v-model="form.password" placeholder="请输入密码" size="large" allow-clear :max-length="30">
|
||||||
v-model="form.password"
|
|
||||||
placeholder="请输入密码"
|
|
||||||
size="large"
|
|
||||||
allow-clear
|
|
||||||
:max-length="30"
|
|
||||||
>
|
|
||||||
<template #prefix><icon-lock /></template>
|
<template #prefix><icon-lock /></template>
|
||||||
</a-input-password>
|
</a-input-password>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -70,13 +54,7 @@
|
|||||||
}
|
}
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input v-model="form.code" placeholder="请输入验证码" size="large" allow-clear :max-length="4">
|
||||||
v-model="form.code"
|
|
||||||
placeholder="请输入验证码"
|
|
||||||
size="large"
|
|
||||||
allow-clear
|
|
||||||
:max-length="4"
|
|
||||||
>
|
|
||||||
<template #prefix><icon-safe /></template>
|
<template #prefix><icon-safe /></template>
|
||||||
<template #append>
|
<template #append>
|
||||||
<verify-code ref="Verify" />
|
<verify-code ref="Verify" />
|
||||||
@@ -85,9 +63,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item :hide-label="true" class="mt-5">
|
<a-form-item :hide-label="true" class="mt-5">
|
||||||
<a-button html-type="submit" type="primary" long size="large" :loading="loading">
|
<a-button html-type="submit" type="primary" long size="large" :loading="loading"> 登录 </a-button>
|
||||||
登录
|
|
||||||
</a-button>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-divider orientation="center">未来登录方式</a-divider>
|
<a-divider orientation="center">未来登录方式</a-divider>
|
||||||
@@ -115,7 +91,7 @@ const router = useRouter()
|
|||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
// 绑定登录form的数据
|
// 绑定登录form的数据
|
||||||
// const form = reactive({ username: "superAdmin", password: "admin123", code: "" })
|
// const form = reactive({ username: "superAdmin", password: "admin123", code: "" })
|
||||||
const form = reactive({ username: "", password: "", code: "" })
|
const form = reactive({ username: "superAdmin", password: "admin123", code: "" })
|
||||||
// 获取验证码dom && arco表单loading
|
// 获取验证码dom && arco表单loading
|
||||||
const Verify = ref(null)
|
const Verify = ref(null)
|
||||||
const loading = ref(null)
|
const loading = ref(null)
|
||||||
@@ -185,7 +161,9 @@ const handleSubmit = async ({ values, errors }) => {
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
margin-top: -255px;
|
margin-top: -255px;
|
||||||
border-radius: var(--border-radius-small);
|
border-radius: var(--border-radius-small);
|
||||||
box-shadow: rgba(0, 0, 0, 0.25) 0px 14px 28px, rgba(0, 0, 0, 0.22) 0px 10px 10px;
|
box-shadow:
|
||||||
|
rgba(0, 0, 0, 0.25) 0px 14px 28px,
|
||||||
|
rgba(0, 0, 0, 0.22) 0px 10px 10px;
|
||||||
backdrop-filter: blur(3px);
|
backdrop-filter: blur(3px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,12 +49,7 @@ export default defineComponent({
|
|||||||
{{
|
{{
|
||||||
default: () => (
|
default: () => (
|
||||||
<div class="preview-container">
|
<div class="preview-container">
|
||||||
<div
|
<div class="button-like" ref={buttonLikeRef} onMouseenter={onMouseenter} onMouseleave={onMouseleave}>
|
||||||
class="button-like"
|
|
||||||
ref={buttonLikeRef}
|
|
||||||
onMouseenter={onMouseenter}
|
|
||||||
onMouseleave={onMouseleave}
|
|
||||||
>
|
|
||||||
<icon-find-replace />
|
<icon-find-replace />
|
||||||
{hoverText.value && <span class="ml-2">{hoverText.value}</span>}
|
{hoverText.value && <span class="ml-2">{hoverText.value}</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import MaInfo from "@/components/ma-info/index.vue"
|
|||||||
import useDutColumn from "@/views/project/round/hooks/useColumn"
|
import useDutColumn from "@/views/project/round/hooks/useColumn"
|
||||||
import useDesignColumn from "@/views/project/dut/hooks/useColumns"
|
import useDesignColumn from "@/views/project/dut/hooks/useColumns"
|
||||||
import useDemandColumn from "@/views/project/design-demand/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() {
|
export default function useKeyToMaInfo() {
|
||||||
|
const route = useRoute()
|
||||||
// 初始状态为空
|
// 初始状态为空
|
||||||
let maInfoDom = ref(<Empty></Empty>)
|
let maInfoDom = ref(<Empty></Empty>)
|
||||||
// 3个列信息
|
// 3个列信息
|
||||||
@@ -17,6 +20,14 @@ export default function useKeyToMaInfo() {
|
|||||||
})
|
})
|
||||||
const designColumns = useDesignColumn(undefined)
|
const designColumns = useDesignColumn(undefined)
|
||||||
const demandColumns = useDemandColumn(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以及是什么类型展示信息
|
// 函数:传入switch后的Promise以及是什么类型展示信息
|
||||||
const fetchNodeDataAndSetMaInfo = async (resPromise: Promise<any>, nodeType: string) => {
|
const fetchNodeDataAndSetMaInfo = async (resPromise: Promise<any>, nodeType: string) => {
|
||||||
const res = await resPromise
|
const res = await resPromise
|
||||||
@@ -42,7 +53,7 @@ export default function useKeyToMaInfo() {
|
|||||||
maInfoDom.value = <MaInfo columns={designColumns.value} data={res.data} tableLayout="auto"></MaInfo>
|
maInfoDom.value = <MaInfo columns={designColumns.value} data={res.data} tableLayout="auto"></MaInfo>
|
||||||
break
|
break
|
||||||
case "demand":
|
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
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
width="80%"
|
width="80%"
|
||||||
draggable
|
draggable
|
||||||
:okLoading="okLoading"
|
:okLoading="okLoading"
|
||||||
|
unmount-on-close
|
||||||
:title="form.name ? form.name : '请填写用例名称'"
|
:title="form.name ? form.name : '请填写用例名称'"
|
||||||
:on-before-ok="handleOkBefore"
|
:on-before-ok="handleOkBefore"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
<script setup lang="jsx">
|
<script setup lang="jsx">
|
||||||
// 本组件位置:1.在右键轮次问题单 2.用例界面关联问题单
|
// 本组件位置:1.在右键轮次问题单 2.用例界面关联问题单
|
||||||
import { ref } from "vue"
|
import { nextTick, ref } from "vue"
|
||||||
import problemApi from "@/api/project/problem"
|
import problemApi from "@/api/project/problem"
|
||||||
import problemSingleApi from "@/api/project/singleProblem"
|
import problemSingleApi from "@/api/project/singleProblem"
|
||||||
import { Message, Notification } from "@arco-design/web-vue"
|
import { Message, Notification } from "@arco-design/web-vue"
|
||||||
@@ -102,12 +102,13 @@ const handleRelatedChange = async (record) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 数据定义
|
// 数据定义
|
||||||
const crudRef = ref()
|
const crudRef = ref(null)
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const caseModalRef = ref()
|
const caseModalRef = ref(null)
|
||||||
|
|
||||||
// 定义open事件
|
// 定义open事件
|
||||||
const open = (row) => {
|
const open = (row) => {
|
||||||
|
nextTick(() => {
|
||||||
if (props.hasRelated === "roundProblem") {
|
if (props.hasRelated === "roundProblem") {
|
||||||
const columnService = crudRef.value.getColumnService()
|
const columnService = crudRef.value.getColumnService()
|
||||||
columnService.get("related").setAttr("hide", true)
|
columnService.get("related").setAttr("hide", true)
|
||||||
@@ -121,6 +122,7 @@ const open = (row) => {
|
|||||||
// 打开时赋值caseInfo
|
// 打开时赋值caseInfo
|
||||||
caseInfo.value = row
|
caseInfo.value = row
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
// crudOptions设置
|
// crudOptions设置
|
||||||
const crudOptions = ref({
|
const crudOptions = ref({
|
||||||
@@ -505,6 +507,7 @@ const columns = ref([
|
|||||||
// 暴露自己的open方法
|
// 暴露自己的open方法
|
||||||
defineExpose({ open })
|
defineExpose({ open })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.alert {
|
.alert {
|
||||||
max-height: 32px;
|
max-height: 32px;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
cancel-text="关闭"
|
cancel-text="关闭"
|
||||||
width="950px"
|
width="950px"
|
||||||
draggable
|
draggable
|
||||||
|
unmount-on-close
|
||||||
>
|
>
|
||||||
<template #title>{{ props.title }}</template>
|
<template #title>{{ props.title }}</template>
|
||||||
<ma-form v-model="form" :columns="columnsOptions" :options="options" ref="crudForm" />
|
<ma-form v-model="form" :columns="columnsOptions" :options="options" ref="crudForm" />
|
||||||
|
|||||||
@@ -17,11 +17,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</ma-crud>
|
</ma-crud>
|
||||||
</div>
|
</div>
|
||||||
<problem-choose
|
<problem-choose ref="problemchoose" @deleted="related_reload" @relatedOrunrelated="related_reload"></problem-choose>
|
||||||
ref="problemchoose"
|
|
||||||
@deleted="related_reload"
|
|
||||||
@relatedOrunrelated="related_reload"
|
|
||||||
></problem-choose>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -33,10 +29,13 @@ import { useTreeDataStore } from "@/store"
|
|||||||
import ProblemChoose from "./components/ProblemChoose.vue"
|
import ProblemChoose from "./components/ProblemChoose.vue"
|
||||||
import { Message } from "@arco-design/web-vue"
|
import { Message } from "@arco-design/web-vue"
|
||||||
import getCaseInfoHook from "@/hooks/workarea/currentCasePage"
|
import getCaseInfoHook from "@/hooks/workarea/currentCasePage"
|
||||||
|
import { useUserStore } from "@/store"
|
||||||
|
|
||||||
const treeDataStore = useTreeDataStore()
|
const treeDataStore = useTreeDataStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
// hook-获取当前用例信息
|
// hook-获取当前用例信息
|
||||||
const { tempCaseInfo, caseIsNotPassedOrNotExe } = getCaseInfoHook()
|
const { tempCaseInfo, caseIsNotPassedOrNotExe, fetchCaseOneStatus } = getCaseInfoHook()
|
||||||
|
|
||||||
// const router = useRouter()
|
// const router = useRouter()
|
||||||
const roundNumber = route.query.key.split("-")[0]
|
const roundNumber = route.query.key.split("-")[0]
|
||||||
@@ -118,6 +117,8 @@ const crudOptions = ref({
|
|||||||
},
|
},
|
||||||
// 请求后置处理-用于新增/删除更新树状的用例关联问题单状态
|
// 请求后置处理-用于新增/删除更新树状的用例关联问题单状态
|
||||||
afterRequest(tableData) {
|
afterRequest(tableData) {
|
||||||
|
// 调用更新是否有未通过变量
|
||||||
|
fetchCaseOneStatus()
|
||||||
const caseQuery = { key: route.query.key }
|
const caseQuery = { key: route.query.key }
|
||||||
treeDataStore.updateCaseTreeData(caseQuery, route.query.id)
|
treeDataStore.updateCaseTreeData(caseQuery, route.query.id)
|
||||||
// 新版本mime必须返回
|
// 新版本mime必须返回
|
||||||
@@ -384,6 +385,7 @@ const crudColumns = ref([
|
|||||||
search: true,
|
search: true,
|
||||||
align: "center",
|
align: "center",
|
||||||
formType: "select",
|
formType: "select",
|
||||||
|
addDefaultValue: userStore.name,
|
||||||
commonRules: [{ required: true, message: "测试人员必填" }],
|
commonRules: [{ required: true, message: "测试人员必填" }],
|
||||||
dict: {
|
dict: {
|
||||||
url: "system/user/list",
|
url: "system/user/list",
|
||||||
|
|||||||
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: 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
349
cdTMP/src/views/project/design-demand/AiModal.vue
Normal file
349
cdTMP/src/views/project/design-demand/AiModal.vue
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
<template>
|
||||||
|
<div class="ai-modal-container">
|
||||||
|
<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">{{
|
||||||
|
generateLoading ? "AI正在生成测试项中..." : "点击生成测试项"
|
||||||
|
}}</a-button>
|
||||||
|
<a-progress
|
||||||
|
:percent="percent"
|
||||||
|
:style="{ width: '100%' }"
|
||||||
|
size="large"
|
||||||
|
:show-text="false"
|
||||||
|
:color="{
|
||||||
|
'0%': 'rgb(var(--primary-6))',
|
||||||
|
'100%': 'rgb(var(--success-6))'
|
||||||
|
}"
|
||||||
|
class="mb-2"
|
||||||
|
/>
|
||||||
|
<a-list :loading="listLoading" :data="dataList">
|
||||||
|
<template #header> 设计需求:{{ designObj?.name ?? "暂无内容" }} </template>
|
||||||
|
<template #item="{ item, index }">
|
||||||
|
<a-list-item>
|
||||||
|
<div class="item-container">
|
||||||
|
<a-input-group>
|
||||||
|
<div class="index-hao">{{ indexTu[index] }}</div>
|
||||||
|
<span class="label">测试项:</span>
|
||||||
|
<a-input placeholder="测试项标识" v-model="item.ident" :style="{ width: '100px' }" @click.stop.prevent></a-input>
|
||||||
|
<a-input placeholder="测试项名称" v-model="item.title" :style="{ width: '250px' }" @click.stop.prevent></a-input>
|
||||||
|
<a-select placeholder="选择优先级" v-model="item.priority" :style="{ width: '150px' }">
|
||||||
|
<a-option value="1">高</a-option>
|
||||||
|
<a-option value="2">中</a-option>
|
||||||
|
<a-option value="3">低</a-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select placeholder="选择测试类型" v-model="item.testType" :style="{ width: '200px' }">
|
||||||
|
<a-option v-for="type in testType" :key="type.key" :value="type.key">
|
||||||
|
{{ type.title }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select placeholder="选择测试手段" multiple v-model="item.testMethod" :style="{ width: '400px' }">
|
||||||
|
<a-option v-for="method in testMethod" :key="method.key" :value="method.key">
|
||||||
|
{{ method.title }}
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-input-group>
|
||||||
|
<div class="m-2 flex justify-start items-center">
|
||||||
|
<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">
|
||||||
|
<table class="arco-table-element" cellpadding="0" cellspacing="0">
|
||||||
|
<thead>
|
||||||
|
<tr class="arco-table-tr">
|
||||||
|
<th class="arco-table-th" :width="100">
|
||||||
|
<span class="arco-table-cell arco-table-cell-align-center">
|
||||||
|
<span class="arco-table-th-title label">子项序号</span>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- 这里tr要v-for渲染 -->
|
||||||
|
<tr class="arco-table-tr" v-for="(row, idx) in item.children" :key="idx">
|
||||||
|
<td class="arco-table-td">
|
||||||
|
<span class="arco-table-cell arco-table-cell-align-center">
|
||||||
|
{{ idx + 1 }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="arco-table-td">
|
||||||
|
<span class="arco-table-cell">
|
||||||
|
<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" />
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-list-item>
|
||||||
|
</template>
|
||||||
|
</a-list>
|
||||||
|
<div class="luButton">
|
||||||
|
<a-button :loading="luButtonLoading" type="primary" @click="luButtonClick">确认录入测试项</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ParentPreview :parent-key="currentKey" />
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onUnmounted, ref } from "vue"
|
||||||
|
import { useRoute } from "vue-router"
|
||||||
|
import designApi from "@/api/project/designDemand"
|
||||||
|
import dictApi from "@/api/common"
|
||||||
|
import OpeAndExpect from "./OpeAndExpect.vue" // 操作和预期子表格
|
||||||
|
import aiApi from "@/api/outs/aiApi"
|
||||||
|
import { Message } from "@arco-design/web-vue"
|
||||||
|
import tool from "@/utils/tool"
|
||||||
|
import { isEmpty } from "lodash-es"
|
||||||
|
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>([])
|
||||||
|
const testMethod = ref<any>([])
|
||||||
|
const fetchTestType = async () => {
|
||||||
|
try {
|
||||||
|
const [typeResponse, methodResponse] = await Promise.all([dictApi.getDict("testType"), dictApi.getDict("testMethod")])
|
||||||
|
testType.value = typeResponse.data
|
||||||
|
testMethod.value = methodResponse.data
|
||||||
|
} catch (e) {
|
||||||
|
Message.error("初始化测试类型或测试手段错误,请检查网络后重试!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchTestType()
|
||||||
|
|
||||||
|
// 初始化设计需求
|
||||||
|
|
||||||
|
const currentKey: string = route.query.key as string
|
||||||
|
const getDesign = async () => {
|
||||||
|
try {
|
||||||
|
const res = await designApi.getDesignDemandOne({ project_id: route.query.id, key: route.query.key })
|
||||||
|
designObj.value = res.data
|
||||||
|
} catch (e) {
|
||||||
|
Message.error("初始化设计需求信息错误,请检查网络后重试!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getDesign()
|
||||||
|
const designObj: any = ref()
|
||||||
|
|
||||||
|
// 进度条和列表加载loading
|
||||||
|
const percent = ref(0.0)
|
||||||
|
const listLoading = ref(false)
|
||||||
|
|
||||||
|
// 根据测试项生成按钮
|
||||||
|
const generateLoading = ref(false)
|
||||||
|
const generateClick = async () => {
|
||||||
|
try {
|
||||||
|
generateLoading.value = true
|
||||||
|
listLoading.value = true
|
||||||
|
percent.value = 0.1 // 开始进度
|
||||||
|
startProgressSimulation()
|
||||||
|
// 变量:给AI的问题
|
||||||
|
const question = tool.htmlToTextWithDOM(designObj.value?.description || "")
|
||||||
|
// 请求后处理结果
|
||||||
|
const res = await aiApi.getAiTestItem({ question: question, stream: false })
|
||||||
|
// 判断真实接口和开发环境接口
|
||||||
|
let tempSolve: any = null
|
||||||
|
if (res.data) {
|
||||||
|
// 说明是开发环境
|
||||||
|
tempSolve = res.data
|
||||||
|
} else {
|
||||||
|
tempSolve = res
|
||||||
|
}
|
||||||
|
const solveRes = JSON.parse(tempSolve.history[0].at(-1))
|
||||||
|
console.log("AI生成测试项结果:", solveRes)
|
||||||
|
// 给Vue渲染测试项
|
||||||
|
dataList.value = solveRes
|
||||||
|
dataList.value.forEach((it: any) => {
|
||||||
|
it.ident = designObj.value.ident
|
||||||
|
it.priority = "1"
|
||||||
|
it.testType = "4"
|
||||||
|
it.testMethod = ["4"]
|
||||||
|
})
|
||||||
|
percent.value = 1.0 // 完成进度
|
||||||
|
Message.success("生成测试项成功,请完善信息后录入数据")
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
percent.value = 0.0
|
||||||
|
} finally {
|
||||||
|
stopProgressSimulation()
|
||||||
|
generateLoading.value = false
|
||||||
|
setTimeout(() => {
|
||||||
|
listLoading.value = false
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成的AI测试项数据
|
||||||
|
const dataList = ref([])
|
||||||
|
|
||||||
|
// 进度条模拟变量和函数
|
||||||
|
const progressInterval = ref<NodeJS.Timeout>()
|
||||||
|
const startProgressSimulation = () => {
|
||||||
|
progressInterval.value = setInterval(() => {
|
||||||
|
if (percent.value < 0.8) {
|
||||||
|
percent.value += (0.8 - percent.value) * 0.1
|
||||||
|
}
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopProgressSimulation = () => {
|
||||||
|
if (progressInterval.value) {
|
||||||
|
clearInterval(progressInterval.value)
|
||||||
|
progressInterval.value = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
stopProgressSimulation()
|
||||||
|
})
|
||||||
|
|
||||||
|
// defineModel
|
||||||
|
const visible = defineModel<boolean>("visible", { default: false })
|
||||||
|
|
||||||
|
// 录入按钮相关
|
||||||
|
const luButtonLoading = ref(false)
|
||||||
|
const emit = defineEmits(["updateTable"])
|
||||||
|
const luButtonClick = async () => {
|
||||||
|
// 1.检查是否还未生成测试项
|
||||||
|
if (isEmpty(dataList.value)) {
|
||||||
|
Message.warning("您还未生成测试项,请生成后再试")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 2.检查测试项标识、测试项名称、优先级、测试类型、测试手段是否填写
|
||||||
|
const testItem: any = dataList.value.at(0)
|
||||||
|
if (!testItem.title.trim()) {
|
||||||
|
Message.warning("请先填写测试项名称!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!testItem.demandDescription.trim()) {
|
||||||
|
Message.warning("请填写测试项描述后再试!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (testItem.testMethod.length === 0) {
|
||||||
|
Message.warning("请先选择测试手段后再试!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 3.组装接口需要的数据
|
||||||
|
const projectId = route.query.id
|
||||||
|
const splitKey: string[] = (route.query.key as any).split("-")
|
||||||
|
const round = splitKey[0]
|
||||||
|
const dut = splitKey[1]
|
||||||
|
const designDemand = splitKey.at(-1)
|
||||||
|
const adequacy: string = "测试用例覆盖测试子项要求的全部内容。\n所有用例执行完毕,对于未执行用例说明未执行原因。"
|
||||||
|
const ident = testItem.ident
|
||||||
|
const name = testItem.title
|
||||||
|
const testType = testItem.testType
|
||||||
|
const testMethod = testItem.testMethod
|
||||||
|
const testDesciption = testItem.demandDescription
|
||||||
|
const priority = testItem.priority
|
||||||
|
const testContent = testItem.children.map(({ name: subName, ...rest }) => ({
|
||||||
|
subName,
|
||||||
|
...rest
|
||||||
|
}))
|
||||||
|
// 4.异步录入啦
|
||||||
|
try {
|
||||||
|
// 首先设置状态
|
||||||
|
luButtonLoading.value = true
|
||||||
|
generateLoading.value = true
|
||||||
|
await demandApi.save({
|
||||||
|
projectId,
|
||||||
|
round,
|
||||||
|
dut,
|
||||||
|
designDemand,
|
||||||
|
adequacy,
|
||||||
|
ident,
|
||||||
|
name,
|
||||||
|
testType,
|
||||||
|
testMethod,
|
||||||
|
testDesciption,
|
||||||
|
priority,
|
||||||
|
testContent
|
||||||
|
})
|
||||||
|
// 请求成功后需要:清除dataList内容,关闭弹窗,提示新增成功,刷新树状结构以及表格(给父组件刷新)
|
||||||
|
dataList.value = []
|
||||||
|
visible.value = false
|
||||||
|
Message.success("录入测试项成功")
|
||||||
|
emit("updateTable")
|
||||||
|
} catch (e) {
|
||||||
|
} finally {
|
||||||
|
luButtonLoading.value = false
|
||||||
|
generateLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 悬浮按钮显示上级设计需求内容
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.index-hao {
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 0 9px;
|
||||||
|
color: rgb(var(--primary-5));
|
||||||
|
}
|
||||||
|
:deep(.arco-list-item) {
|
||||||
|
border: 1px solid #999 !important;
|
||||||
|
}
|
||||||
|
:deep(.arco-progress-line-bar) {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
:deep(.arco-progress-line) {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.luButton {
|
||||||
|
margin-left: auto;
|
||||||
|
padding: 10px;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -48,9 +48,9 @@ const DemandSubForm = defineComponent({
|
|||||||
// Dom
|
// Dom
|
||||||
return () => (
|
return () => (
|
||||||
// 注意v-model:visible是不能放在对象解构的
|
// 注意v-model:visible是不能放在对象解构的
|
||||||
<a-modal {...modalOptions} v-model:visible={visible.value} on-before-cancel={handleBeforeCancel} width='86%'>
|
<a-modal {...modalOptions} v-model:visible={visible.value} on-before-cancel={handleBeforeCancel} width='86%' unmount-on-close>
|
||||||
{{
|
{{
|
||||||
title: () => <span>[设计需求]-{title.value}</span>,
|
title: () => <span>[测试项]-{title.value}</span>,
|
||||||
default: () => (
|
default: () => (
|
||||||
<ma-form
|
<ma-form
|
||||||
ref={formRef}
|
ref={formRef}
|
||||||
|
|||||||
47
cdTMP/src/views/project/design-demand/OpeAndExpect.vue
Normal file
47
cdTMP/src/views/project/design-demand/OpeAndExpect.vue
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<div class="arco-table arco-table-size-large arco-table-border arco-table-stripe arco-table-hover">
|
||||||
|
<div class="arco-table-container">
|
||||||
|
<table class="arco-table-element" cellpadding="0" cellspacing="0">
|
||||||
|
<thead>
|
||||||
|
<tr class="arco-table-tr">
|
||||||
|
<th class="arco-table-th" :width="400">
|
||||||
|
<span class="arco-table-cell arco-table-cell-align-center">
|
||||||
|
<span class="arco-table-th-title label">操作</span>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th class="arco-table-th" :width="400">
|
||||||
|
<span class="arco-table-cell arco-table-cell-align-center">
|
||||||
|
<span class="arco-table-th-title label">预期</span>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="arco-table-tr" v-for="(record, idx) in modelValue" :key="idx">
|
||||||
|
<td class="arco-table-td">
|
||||||
|
<span class="arco-table-cell arco-table-cell-align-center">
|
||||||
|
<a-textarea auto-size placeholder="请填写步骤" v-model="record.operation"></a-textarea>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="arco-table-td">
|
||||||
|
<span class="arco-table-cell arco-table-cell-align-center">
|
||||||
|
<a-textarea auto-size placeholder="请填写预期" v-model="record.expect"></a-textarea>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const modelValue = defineModel<
|
||||||
|
{
|
||||||
|
operation: string
|
||||||
|
expect: string
|
||||||
|
}[]
|
||||||
|
>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import PinYinMatch from "pinyin-match"
|
import PinYinMatch from "pinyin-match"
|
||||||
|
import tool from "@/utils/tool"
|
||||||
|
import { useRoute } from "vue-router"
|
||||||
|
|
||||||
export default function (crudOrFormRef: any) {
|
export default function (crudOrFormRef: any) {
|
||||||
|
const route = useRoute()
|
||||||
|
const isFpga = tool.checkForCpuOrFPGA(route.query.plant_type)
|
||||||
const crudColumns = ref([
|
const crudColumns = ref([
|
||||||
{
|
{
|
||||||
title: "ID",
|
title: "ID",
|
||||||
@@ -83,8 +87,7 @@ export default function (crudOrFormRef: any) {
|
|||||||
formType: "textarea",
|
formType: "textarea",
|
||||||
maxLength: 256,
|
maxLength: 256,
|
||||||
commonRules: [{ required: true, message: "充分性描述必填" }],
|
commonRules: [{ required: true, message: "充分性描述必填" }],
|
||||||
addDefaultValue:
|
addDefaultValue: "测试用例覆盖XX子项名称1、XX子项名称2、XX子项名称3子项要求的全部内容。\n所有用例执行完毕,对于未执行的用例说明未执行原因。"
|
||||||
"测试用例覆盖XX子项名称1、XX子项名称2、XX子项名称3子项要求的全部内容。\n所有用例执行完毕,对于未执行的用例说明未执行原因。"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "测试项描述",
|
title: "测试项描述",
|
||||||
@@ -92,7 +95,10 @@ export default function (crudOrFormRef: any) {
|
|||||||
dataIndex: "testDesciption",
|
dataIndex: "testDesciption",
|
||||||
formType: "textarea",
|
formType: "textarea",
|
||||||
maxLength: 256,
|
maxLength: 256,
|
||||||
placeholder: "请填写整体测试项的描述"
|
placeholder: "FPGA填写-请填写整体测试项的描述",
|
||||||
|
display: isFpga,
|
||||||
|
addDisplay: isFpga,
|
||||||
|
editDisplay: isFpga
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "测试子项",
|
title: "测试子项",
|
||||||
@@ -119,6 +125,15 @@ export default function (crudOrFormRef: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "测试项描述",
|
||||||
|
dataIndex: "subDescription",
|
||||||
|
placeholder: "非FPGA填写每个子项一句话描述",
|
||||||
|
formType:"textarea",
|
||||||
|
display: !isFpga,
|
||||||
|
addDisplay: !isFpga,
|
||||||
|
editDisplay: !isFpga
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "操作与预期",
|
title: "操作与预期",
|
||||||
dataIndex: "subStep",
|
dataIndex: "subStep",
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ export default function (crudRef: Ref<InstanceType<typeof MaCrud>>) {
|
|||||||
tablePagination: false,
|
tablePagination: false,
|
||||||
operationColumnWidth: 200,
|
operationColumnWidth: 200,
|
||||||
operationColumn: true,
|
operationColumn: true,
|
||||||
|
draggable: false,
|
||||||
operationColumnAlign: "center",
|
operationColumnAlign: "center",
|
||||||
formOption: {
|
formOption: {
|
||||||
width: "86%",
|
width: "86%",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
:options="crudOptions"
|
:options="crudOptions"
|
||||||
:columns="crudColumns"
|
:columns="crudColumns"
|
||||||
ref="crudRef"
|
ref="crudRef"
|
||||||
|
id="basic-table-demand-normal"
|
||||||
@beforeCancel="handleBeforeCancel"
|
@beforeCancel="handleBeforeCancel"
|
||||||
:parent-key="route.query.key"
|
:parent-key="route.query.key"
|
||||||
>
|
>
|
||||||
@@ -14,19 +15,27 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- 表格前置扩展槽:添加关联按钮 -->
|
<!-- 表格前置扩展槽:添加关联按钮 -->
|
||||||
<template #tableAfterButtons>
|
<template #tableAfterButtons>
|
||||||
|
<a-space>
|
||||||
<a-button type="outline" status="warning" @click="handleOpenRelationCSX">
|
<a-button type="outline" status="warning" @click="handleOpenRelationCSX">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-tags />
|
<icon-tags />
|
||||||
</template>
|
</template>
|
||||||
关联测试项
|
关联测试项
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<a-divider direction="vertical" type="double" />
|
||||||
|
<a-button type="outline" @click="handleReplaceClick">批量替换</a-button>
|
||||||
|
<a-divider direction="vertical" type="double" />
|
||||||
|
<a-button type="outline" @click="handleOpenReplacePriority">批量修改优先级</a-button>
|
||||||
|
<a-divider direction="vertical" type="double" />
|
||||||
|
<AiButton @click="handleAiButtonClick" />
|
||||||
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
<!-- 版本字段的插槽 -->
|
<!-- 版本字段的插槽 -->
|
||||||
<template #inputPrepend-ident> XQ_XX_ </template>
|
<template #inputPrepend-ident> XQ_XX_ </template>
|
||||||
</ma-crud>
|
</ma-crud>
|
||||||
</div>
|
</div>
|
||||||
<!-- 关联的modal组件 -->
|
<!-- 关联的modal组件 -->
|
||||||
<a-modal v-model:visible="visible" width="700px" draggable :on-before-ok="handleRelatedOk">
|
<a-modal v-model:visible="visible" width="700px" draggable unmount-on-close :on-before-ok="handleRelatedOk">
|
||||||
<template #title>关联测试项</template>
|
<template #title>关联测试项</template>
|
||||||
<div class="pb-3">已存在的关联项:</div>
|
<div class="pb-3">已存在的关联项:</div>
|
||||||
<a-typography-paragraph>
|
<a-typography-paragraph>
|
||||||
@@ -44,6 +53,23 @@
|
|||||||
v-model:model-value="relatedData"
|
v-model:model-value="relatedData"
|
||||||
/>
|
/>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
<ReplaceModel
|
||||||
|
ref="replaceModal"
|
||||||
|
:api="demandApi.replace"
|
||||||
|
:columns="[
|
||||||
|
{ dataIndex: 'ident', title: '标识' },
|
||||||
|
{ dataIndex: 'name', title: '名称' },
|
||||||
|
{ dataIndex: 'testDesciption', title: '测试描述' },
|
||||||
|
{ dataIndex: 'testContent', title: '测试子项' }
|
||||||
|
]"
|
||||||
|
key="modal-demand-normal"
|
||||||
|
popup-key="demand-normal"
|
||||||
|
@replaceSuccess="replaceSuccessHandle"
|
||||||
|
/>
|
||||||
|
<!-- 批量修改优先级 -->
|
||||||
|
<ReplacePriority @modifySuccess="crudRef.refresh()" ref="replacePriorityRef" />
|
||||||
|
<!-- AI-Modal -->
|
||||||
|
<AiModal @updateTable="handleAiRefresh" v-model:visible="ai_modal_visible"></AiModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -51,20 +77,48 @@
|
|||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import commonApi from "@/api/common"
|
import commonApi from "@/api/common"
|
||||||
import { useRoute } from "vue-router"
|
import { useRoute } from "vue-router"
|
||||||
|
import { Message } from "@arco-design/web-vue"
|
||||||
|
import AiButton from "@/components/ai-button/index.vue"
|
||||||
|
import AiModal from "./AiModal.vue"
|
||||||
// hooks
|
// hooks
|
||||||
|
import { useTreeDataStore } from "@/store"
|
||||||
import useCrudOpMore from "./hooks/useCrudOpMore"
|
import useCrudOpMore from "./hooks/useCrudOpMore"
|
||||||
import useColumn from "./hooks/useColumns"
|
import useColumn from "./hooks/useColumns"
|
||||||
import useRalateDemand from "./hooks/useRalateDemand"
|
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
|
// inits
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const treeDataStore = useTreeDataStore()
|
||||||
|
|
||||||
// refs
|
// refs
|
||||||
const crudRef = ref(null)
|
const crudRef = ref(null)
|
||||||
|
|
||||||
|
// 2025年5月新增
|
||||||
|
const replaceModal = ref()
|
||||||
|
const handleReplaceClick = () => {
|
||||||
|
replaceModal.value?.open(crudRef.value.getSelecteds) // 把获取选中行的函数给传递给替换组件
|
||||||
|
}
|
||||||
|
const replaceSuccessHandle = async (count) => {
|
||||||
|
Message.success(`批量更新成功,尝试更新行数:${count}`)
|
||||||
|
// 批量更新后刷新表格
|
||||||
|
crudRef.value.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2025-05新增-批量修改优先级
|
||||||
|
const replacePriorityRef = ref(null)
|
||||||
|
const handleOpenReplacePriority = () => {
|
||||||
|
replacePriorityRef.value?.open(crudRef.value.getSelecteds)
|
||||||
|
}
|
||||||
|
|
||||||
// 根据传参获取key,分别为轮次、设计需求的key
|
// 根据传参获取key,分别为轮次、设计需求的key
|
||||||
const { projectId, crudOptions, handleBeforeCancel } = useCrudOpMore(crudRef)
|
const { projectId, crudOptions, handleBeforeCancel } = useCrudOpMore(crudRef)
|
||||||
const crudColumns = useColumn(crudRef)
|
const crudColumns = useColumn(crudRef)
|
||||||
|
|
||||||
// 关联弹窗、关联的事件处理
|
// 关联弹窗、关联的事件处理
|
||||||
const { visible, relatedData, options, cascaderLoading, computedRelatedData, handleOpenRelationCSX, handleRelatedOk } =
|
const { visible, relatedData, options, cascaderLoading, computedRelatedData, handleOpenRelationCSX, handleRelatedOk } = useRalateDemand(projectId)
|
||||||
useRalateDemand(projectId)
|
|
||||||
|
|
||||||
// 标识显示字段
|
// 标识显示字段
|
||||||
const testTypeDict = ref([])
|
const testTypeDict = ref([])
|
||||||
@@ -83,9 +137,18 @@ const showType = (record) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AI-MODAL
|
||||||
|
const ai_modal_visible = ref(false)
|
||||||
|
const handleAiButtonClick = () => {
|
||||||
|
ai_modal_visible.value = true
|
||||||
|
}
|
||||||
|
const handleAiRefresh = () => {
|
||||||
|
refreshCrudTable()
|
||||||
|
}
|
||||||
// 暴露给route-view的刷新表格函数
|
// 暴露给route-view的刷新表格函数
|
||||||
const refreshCrudTable = () => {
|
const refreshCrudTable = () => {
|
||||||
crudRef.value.refresh()
|
crudRef.value.refresh()
|
||||||
|
treeDataStore.updateTestDemandTreeData({ key: route.query.key + "-0" }, projectId.value)
|
||||||
}
|
}
|
||||||
defineExpose({ refreshCrudTable })
|
defineExpose({ refreshCrudTable })
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -97,4 +160,11 @@ defineOptions({
|
|||||||
.ol-reset {
|
.ol-reset {
|
||||||
list-style: auto;
|
list-style: auto;
|
||||||
}
|
}
|
||||||
|
/* 下面让modal的蒙层不交互,让用户可以复制table的文字 */
|
||||||
|
div:deep(.arco-modal-container) {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
:deep(.arco-modal.arco-modal-draggable) {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -40,16 +40,11 @@ const DesignSubForm = defineComponent({
|
|||||||
// Dom
|
// Dom
|
||||||
return () => (
|
return () => (
|
||||||
// 注意v-model:visible是不能放在对象解构的
|
// 注意v-model:visible是不能放在对象解构的
|
||||||
<a-modal {...modalOptions} v-model:visible={visible.value}>
|
<a-modal {...modalOptions} v-model:visible={visible.value} unmount-on-close>
|
||||||
{{
|
{{
|
||||||
title: () => <span>[设计需求]-{title.value}</span>,
|
title: () => <span>[设计需求]-{title.value}</span>,
|
||||||
default: () => (
|
default: () => (
|
||||||
<ma-form
|
<ma-form ref={formRef} v-model={formData.value} options={options.value} columns={columnOptions.value}>
|
||||||
ref={formRef}
|
|
||||||
v-model={formData.value}
|
|
||||||
options={options.value}
|
|
||||||
columns={columnOptions.value}
|
|
||||||
>
|
|
||||||
{{
|
{{
|
||||||
"inputPrepend-ident": () => <span>SJ-XX-</span>
|
"inputPrepend-ident": () => <span>SJ-XX-</span>
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import tool from "@/utils/tool"
|
|||||||
import useColumn from "../hooks/useColumns"
|
import useColumn from "../hooks/useColumns"
|
||||||
|
|
||||||
// 设置不同ma-form选项
|
// 设置不同ma-form选项
|
||||||
export default function useOptions(formRef: any) {
|
export default function useOptions(formRef?: any) {
|
||||||
const options = ref({
|
const options = ref({
|
||||||
showButtons: false,
|
showButtons: false,
|
||||||
labelAlign: "center"
|
labelAlign: "center"
|
||||||
})
|
})
|
||||||
const crudColumns = useColumn(formRef)
|
const crudColumns = useColumn()
|
||||||
const columnOptions = computed(() => {
|
const columnOptions = computed(() => {
|
||||||
return tool.renameKeyInArray(crudColumns.value, "commonRules", "rules")
|
return tool.renameKeyInArray(crudColumns.value, "commonRules", "rules")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<div class="uploadContainer">
|
<div class="uploadContainer">
|
||||||
<span :style="{ marginBottom: '10px', flex: '0 1 150px' }">上传需求.docx:</span>
|
<span :style="{ marginBottom: '10px', flex: '0 1 150px' }">上传需求.docx:</span>
|
||||||
<a-upload
|
<a-upload
|
||||||
:style="{ marginBottom: '10px' }"
|
:style="{ marginBottom: '10px', marginLeft: '9px' }"
|
||||||
:limit="1"
|
:limit="1"
|
||||||
accept=".docx"
|
accept=".docx"
|
||||||
:action="`/api/dut_upload/upload_xq_docx/?parseChapter=${parseChapter}`"
|
:action="`/api/dut_upload/upload_xq_docx/?parseChapter=${parseChapter}`"
|
||||||
@@ -24,16 +24,15 @@
|
|||||||
></a-upload>
|
></a-upload>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<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>
|
<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-select allow-search v-model="selectValue">
|
||||||
<a-option v-for="item in demandType" :key="item.key" :value="item.key">{{ item.title }}</a-option>
|
<a-option v-for="item in demandType" :key="item.key" :value="item.key">{{ item.title }}</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</div>
|
</div>
|
||||||
<a-alert :style="{ margin: '10px 0' }" type="warning">
|
<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>以上保存后上传
|
<span class="important-text">引用 -> 目录 -> 自定义目录 -> 显示级别改为6</span>以上保存后上传
|
||||||
</a-alert>
|
</a-alert>
|
||||||
<div class="operation-container">
|
<div class="operation-container">
|
||||||
@@ -45,60 +44,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<a-spin :loading="loading" tip="解析word完成,正在渲染界面..." :style="{ width: '100%' }">
|
<a-spin :loading="loading" tip="解析word完成,正在渲染界面..." :style="{ width: '100%' }">
|
||||||
<div class="demand-container">
|
<div class="demand-container">
|
||||||
<a-list
|
<a-list @page-change="handlePageChange" :data="htmlData" :pagination-props="{ defaultPageSize: 15, total: htmlData.length }">
|
||||||
@page-change="handlePageChange"
|
|
||||||
:data="htmlData"
|
|
||||||
:pagination-props="{ defaultPageSize: 15, total: htmlData.length }"
|
|
||||||
>
|
|
||||||
<template #item="{ item, index }">
|
<template #item="{ item, index }">
|
||||||
<a-list-item>
|
<a-list-item>
|
||||||
<div class="item-container">
|
<div class="item-container">
|
||||||
<a-input-group>
|
<a-input-group>
|
||||||
<a-input
|
<a-input placeholder="章节号" v-model="item.chapter" :style="{ width: '100px' }" @click.stop.prevent></a-input>
|
||||||
placeholder="章节号"
|
<a-input placeholder="标题" v-model="item.title" :style="{ width: '300px' }" @click.stop.prevent></a-input>
|
||||||
v-model="item.chapter"
|
<a-input placeholder="标识" v-model="item.ident" :style="{ width: '200px' }" @click.stop.prevent></a-input>
|
||||||
:style="{ width: '100px' }"
|
<a-select :style="{ width: '150px' }" placeholder="请选择设计需求类型" @click.stop.prevent v-model="item.demandType">
|
||||||
@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-option v-for="it in demandType" :value="it.key">{{ it.title }}</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
<a-button-group>
|
<a-button-group>
|
||||||
<a-button
|
<a-button type="primary" status="success" size="small" @click.stop.prevent="handledownCreate(index)">
|
||||||
type="primary"
|
|
||||||
status="success"
|
|
||||||
size="small"
|
|
||||||
@click.stop.prevent="handledownCreate(index)"
|
|
||||||
>
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-plus />
|
<icon-plus />
|
||||||
</template>
|
</template>
|
||||||
下方新增
|
下方新增
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button
|
<a-button type="primary" status="danger" size="small" @click.stop.prevent="handleDelete(index)">
|
||||||
type="primary"
|
|
||||||
status="danger"
|
|
||||||
size="small"
|
|
||||||
@click.stop.prevent="handleDelete(index)"
|
|
||||||
>
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-delete />
|
<icon-delete />
|
||||||
</template>
|
</template>
|
||||||
@@ -146,8 +111,7 @@ const modalVisible = ref(false)
|
|||||||
const htmlData = ref([])
|
const htmlData = ref([])
|
||||||
|
|
||||||
// ~~~~1.list~~~~
|
// ~~~~1.list~~~~
|
||||||
const { loading, handleCreateAtLatest, handleResetData, handledownCreate, handlePageChange, handleDelete } =
|
const { loading, handleCreateAtLatest, handleResetData, handledownCreate, handlePageChange, handleDelete } = useListOperaton(htmlData)
|
||||||
useListOperaton(htmlData)
|
|
||||||
|
|
||||||
// ~~~~2.upload~~~~
|
// ~~~~2.upload~~~~
|
||||||
const { handleUploadSuccess, handleUploadError, parseChapter, selectValue } = useUpload(htmlData)
|
const { handleUploadSuccess, handleUploadError, parseChapter, selectValue } = useUpload(htmlData)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
|
|
||||||
export default function (crudOrFormRef: any) {
|
export default function (crudOrFormRef?: any) {
|
||||||
const crudColumns = ref([
|
const crudColumns = ref([
|
||||||
{
|
{
|
||||||
title: "ID",
|
title: "ID",
|
||||||
|
|||||||
@@ -36,15 +36,15 @@ export default function (crudRef: Ref<InstanceType<typeof MaCrud>>) {
|
|||||||
crudRef.value.crudFormRef.actionTitle = `${route.query.ident} > ${(td[round_key] as any).title} > ${(td[round_key] as any).children[dut_key].title} >设计需求[${record.name}]-`
|
crudRef.value.crudFormRef.actionTitle = `${route.query.ident} > ${(td[round_key] as any).title} > ${(td[round_key] as any).children[dut_key].title} >设计需求[${record.name}]-`
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
afterAdd: (res) => {
|
afterAdd: (res: any) => {
|
||||||
let id = projectId.value
|
let id = projectId.value
|
||||||
treeDataStore.updateDesignDemandTreeData(res.data, id)
|
treeDataStore.updateDesignDemandTreeData(res.data, id)
|
||||||
},
|
},
|
||||||
afterEdit: (res) => {
|
afterEdit: (res: any) => {
|
||||||
let id = projectId.value
|
let id = projectId.value
|
||||||
treeDataStore.updateDesignDemandTreeData(res.data, id)
|
treeDataStore.updateDesignDemandTreeData(res.data, id)
|
||||||
},
|
},
|
||||||
afterDelete: (res, record) => {
|
afterDelete: (_: any, record: any) => {
|
||||||
let id = projectId.value
|
let id = projectId.value
|
||||||
if (!record) {
|
if (!record) {
|
||||||
record = { key: route.query.key + "-X" }
|
record = { key: route.query.key + "-X" }
|
||||||
@@ -62,12 +62,12 @@ export default function (crudRef: Ref<InstanceType<typeof MaCrud>>) {
|
|||||||
rowSelection: { showCheckedAll: true },
|
rowSelection: { showCheckedAll: true },
|
||||||
searchColNumber: 4,
|
searchColNumber: 4,
|
||||||
tablePagination: false,
|
tablePagination: false,
|
||||||
operationColumnWidth: 250,
|
operationColumnWidth: 300,
|
||||||
operationColumn: true,
|
operationColumn: true,
|
||||||
operationColumnAlign: "center",
|
operationColumnAlign: "center",
|
||||||
formOption: {
|
formOption: {
|
||||||
width: 1200
|
width: 1200
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
return crudOptions
|
return crudOptions
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,38 +2,103 @@
|
|||||||
<div class="ma-content-block lg:flex justify-between p-4">
|
<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">
|
<div class="lg:w-full w-full lg:ml-4 mt-5 lg:mt-0">
|
||||||
<!-- CRUD组件 -->
|
<!-- CRUD组件 -->
|
||||||
<ma-crud :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 }">
|
<template #ident="{ record }">
|
||||||
{{ showType(record) }}
|
{{ showType(record) }}
|
||||||
</template>
|
</template>
|
||||||
<template #tableAfterButtons>
|
<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>
|
<template #icon>
|
||||||
<icon-plus />
|
<icon-plus />
|
||||||
</template>
|
</template>
|
||||||
上传需求规格说明快捷录入
|
上传需求规格说明快捷录入
|
||||||
</a-button>
|
</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>
|
</template>
|
||||||
<!-- 字段的前缀后缀的插槽 -->
|
<!-- 字段的前缀后缀的插槽 -->
|
||||||
<!-- 版本字段的插槽 -->
|
<!-- 版本字段的插槽 -->
|
||||||
<template #inputPrepend-ident> SJ-XX- </template>
|
<template #inputPrepend-ident> SJ-XX- </template>
|
||||||
|
<!-- 操作列前置插槽 -->
|
||||||
|
<template #operationBeforeExtend="{ record }">
|
||||||
|
<a-link @click="handleCopyCurrentNode($event, record)" :loading="copyLoading"><icon-copy />复制当前</a-link>
|
||||||
|
</template>
|
||||||
</ma-crud>
|
</ma-crud>
|
||||||
</div>
|
</div>
|
||||||
<file-input-modal ref="fileInputRef" @enterFinish="crudRef.refresh()"></file-input-modal>
|
<file-input-modal ref="fileInputRef" @enterFinish="crudRef.refresh()"></file-input-modal>
|
||||||
|
<!-- 批量替换组件 -->
|
||||||
|
<ReplaceModel
|
||||||
|
ref="replaceModal"
|
||||||
|
:api="designApi.replace"
|
||||||
|
:columns="[
|
||||||
|
{ dataIndex: 'ident', title: '标识' },
|
||||||
|
{ dataIndex: 'name', title: '名称' },
|
||||||
|
{ dataIndex: 'chapter', title: '章节号' },
|
||||||
|
{ dataIndex: 'description', title: '需求描述' }
|
||||||
|
]"
|
||||||
|
key="modal-design-normal"
|
||||||
|
popup-key="design-normal"
|
||||||
|
@replaceSuccess="replaceSuccessHandle"
|
||||||
|
/>
|
||||||
|
<!-- 批量新增设计需求组件 -->
|
||||||
|
<BatchDesginCreate ref="batchCreateRef" :typeDict="demandTypeDict" @batchCreateFinish="refreshCrudTable" />
|
||||||
|
<!-- 批量创建测试项组件 -->
|
||||||
|
<BatchDemandCreate ref="batchCreateDemandRef" @batchDemandCreateComplete="refreshCrudTable" />
|
||||||
|
<!-- 批量创建测试用例组件 -->
|
||||||
|
<BatchCaseCreate ref="batchCreateCaseRef" @batchCaseCreateComplete="refreshCrudTable" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
|
import { useTreeDataStore } from "@/store"
|
||||||
import useCrudOptions from "@/views/project/dut/hooks/useCrudOptions"
|
import useCrudOptions from "@/views/project/dut/hooks/useCrudOptions"
|
||||||
import useColumns from "./hooks/useColumns"
|
import useColumns from "./hooks/useColumns"
|
||||||
|
import { Message } from "@arco-design/web-vue"
|
||||||
import { useRoute } from "vue-router"
|
import { useRoute } from "vue-router"
|
||||||
import dutApi from "@/api/project/dut"
|
import dutApi from "@/api/project/dut"
|
||||||
|
import designApi from "@/api/project/designDemand"
|
||||||
import commonApi from "@/api/common"
|
import commonApi from "@/api/common"
|
||||||
import FileInputModal from "./components/FileInputModal/index.vue"
|
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 route = useRoute()
|
||||||
const crudRef = ref()
|
const crudRef = ref()
|
||||||
const projectId = ref(route.query.id)
|
const projectId = ref(route.query.id)
|
||||||
|
const treeDataStore = useTreeDataStore()
|
||||||
|
|
||||||
|
// 5月28日新增功能:替换
|
||||||
|
const replaceModal = ref()
|
||||||
|
// 12月16日新增功能:批量添加
|
||||||
|
const batchCreateRef = ref()
|
||||||
|
const batchCreateDemandRef = ref()
|
||||||
|
const batchCreateCaseRef = ref()
|
||||||
|
|
||||||
|
const handleReplaceClick = () => {
|
||||||
|
replaceModal.value?.open(crudRef.value.getSelecteds) // 把获取选中行的函数给传递给替换组件
|
||||||
|
}
|
||||||
|
|
||||||
|
const replaceSuccessHandle = async (count) => {
|
||||||
|
Message.success(`批量更新成功,尝试更新行数:${count}`)
|
||||||
|
// 批量更新后刷新表格
|
||||||
|
crudRef.value.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
// 5月8日修改设计需求标识就按SJ-FT-设计需求标识来
|
// 5月8日修改设计需求标识就按SJ-FT-设计需求标识来
|
||||||
const demandTypeDict = ref([])
|
const demandTypeDict = ref([])
|
||||||
;(function () {
|
;(function () {
|
||||||
@@ -62,12 +127,45 @@ const showType = (record) => {
|
|||||||
// crud组件
|
// crud组件
|
||||||
const crudOptions = useCrudOptions(crudRef)
|
const crudOptions = useCrudOptions(crudRef)
|
||||||
const crudColumns = useColumns(crudRef)
|
const crudColumns = useColumns(crudRef)
|
||||||
|
|
||||||
// ~~~大功能打开ma-form-modal~~~
|
// ~~~大功能打开ma-form-modal~~~
|
||||||
const fileInputRef = ref(null)
|
const fileInputRef = ref(null)
|
||||||
const handleAddFileInputDemand = () => {
|
const handleAddFileInputDemand = () => {
|
||||||
fileInputRef.value.open()
|
fileInputRef.value.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ~~~批量新增设计需求弹窗~~~
|
||||||
|
const handleBatchCreate = () => {
|
||||||
|
batchCreateRef.value.open({})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~~~批量新增测试项弹窗~~~
|
||||||
|
const handleBatchDemandCreate = () => {
|
||||||
|
batchCreateDemandRef.value.open({})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ~~~批量新增测试用例弹窗~~~
|
||||||
|
const handleBatchCaseCreate = () => {
|
||||||
|
batchCreateCaseRef.value.open({})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制设计需求到当前dut
|
||||||
|
const copyLoading = ref(false)
|
||||||
|
const handleCopyCurrentNode = async (_, record) => {
|
||||||
|
copyLoading.value = true
|
||||||
|
try {
|
||||||
|
await designApi.copy_current({ dut_id: record.dut.id, design_id: record.id })
|
||||||
|
// 复制成功给提示
|
||||||
|
Message.success("复制成功!")
|
||||||
|
refreshCrudTable()
|
||||||
|
treeDataStore.updateDesignDemandTreeData({ key: record.key }, projectId.value)
|
||||||
|
} catch (e) {
|
||||||
|
console.log("复制失败,后台打印错误:", e)
|
||||||
|
} finally {
|
||||||
|
copyLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const refreshCrudTable = () => {
|
const refreshCrudTable = () => {
|
||||||
crudRef.value.refresh()
|
crudRef.value.refresh()
|
||||||
}
|
}
|
||||||
@@ -78,4 +176,12 @@ defineOptions({
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped>
|
||||||
|
/* 下面让modal的蒙层不交互,让用户可以复制table的文字 */
|
||||||
|
div:deep(.arco-modal-container) {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
:deep(.arco-modal.arco-modal-draggable) {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -8,13 +8,15 @@
|
|||||||
import DesignTable from "../components/DesignTable/index.vue"
|
import DesignTable from "../components/DesignTable/index.vue"
|
||||||
import DemandTable from "../components/DemandTable/index.vue"
|
import DemandTable from "../components/DemandTable/index.vue"
|
||||||
import CaseTable from "../components/CaseTable/index.vue"
|
import CaseTable from "../components/CaseTable/index.vue"
|
||||||
|
import ProblemTable from "../components/ProblemTable/index.vue"
|
||||||
|
|
||||||
/* 导入columns看能不能行 */
|
/* 导入columns看能不能行 */
|
||||||
// useColumns使用对应关系
|
// useColumns使用对应关系
|
||||||
const mapColumn = {
|
const mapColumn = {
|
||||||
design: DesignTable,
|
design: DesignTable,
|
||||||
demand: DemandTable,
|
demand: DemandTable,
|
||||||
case: CaseTable
|
case: CaseTable,
|
||||||
|
problem: ProblemTable
|
||||||
}
|
}
|
||||||
// props
|
// props
|
||||||
const { type } = defineProps<{
|
const { type } = defineProps<{
|
||||||
@@ -25,7 +27,7 @@ const { type } = defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.pro-table-container{
|
.pro-table-container {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,20 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 注意该组件强制绑定caseTable页面,不要使用在其他地方了 -->
|
<!-- 注意该组件强制绑定caseTable页面,不要使用在其他地方了 -->
|
||||||
<div class="replace-person-container">
|
<div class="replace-person-container">
|
||||||
<a-modal
|
<a-modal v-model:visible="visible" width="40%" unmount-on-close ok-text="替换执行时间" cancel-text="关闭" draggable :on-before-ok="submitReplace">
|
||||||
v-model:visible="visible"
|
|
||||||
width="40%"
|
|
||||||
unmount-on-close
|
|
||||||
ok-text="替换执行时间"
|
|
||||||
cancel-text="关闭"
|
|
||||||
draggable
|
|
||||||
:on-before-ok="submitReplace"
|
|
||||||
>
|
|
||||||
<template #title>替换执行时间</template>
|
<template #title>替换执行时间</template>
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<a-form ref="exeTime" :model="formData" scroll-to-first-error>
|
<a-form ref="exeTime" :model="formData" scroll-to-first-error>
|
||||||
<a-form-item field="designPerson" label="执行时间">
|
<a-form-item field="exetime" label="执行时间">
|
||||||
<a-date-picker v-model="exetime" style="width: 200px" />
|
<a-range-picker v-model="exetime" style="width: 400px" :shortcuts="shortcuts" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,37 +18,67 @@
|
|||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import { Message } from "@arco-design/web-vue"
|
import { Message } from "@arco-design/web-vue"
|
||||||
import caseApi from "@/api/project/case"
|
import caseApi from "@/api/project/case"
|
||||||
|
import { isEmpty } from "lodash-es"
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const formData = ref({})
|
const formData = ref({})
|
||||||
const exetime = ref("")
|
const exetime = ref<string[]>([])
|
||||||
|
const getSelectedsFunc = ref<any>(() => [])
|
||||||
|
|
||||||
// props
|
// props
|
||||||
const { selectRows } = defineProps<{ selectRows: any }>()
|
const { selectRows } = defineProps<{ selectRows?: any }>()
|
||||||
|
|
||||||
|
// emits
|
||||||
|
const emit = defineEmits(["modifySuccess"])
|
||||||
|
|
||||||
// 2.异步执行替换操作,返回boolean-true则关闭弹窗
|
// 2.异步执行替换操作,返回boolean-true则关闭弹窗
|
||||||
const submitReplace = async () => {
|
const submitReplace = async () => {
|
||||||
// 不再非受控验证,手动验证
|
// 不再非受控验证,手动验证
|
||||||
if (exetime.value === "") {
|
if (isEmpty(exetime.value)) {
|
||||||
Message.error("请选择时间后,进行替换操作")
|
Message.error("请选择时间后,进行替换操作")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (selectRows.length && selectRows.length > 0) {
|
const selecteds = selectRows || getSelectedsFunc.value() || []
|
||||||
|
if (selecteds.length && selecteds.length > 0) {
|
||||||
// 判断是否选择了行
|
// 判断是否选择了行
|
||||||
// 请求后台执行
|
// 请求后台执行
|
||||||
|
try {
|
||||||
await caseApi.exetimeReplace({
|
await caseApi.exetimeReplace({
|
||||||
selectRows: selectRows,
|
selectRows: selecteds,
|
||||||
exetime: exetime.value
|
exetime: exetime.value as any
|
||||||
})
|
})
|
||||||
|
emit("modifySuccess")
|
||||||
Message.success("批量替换成功...")
|
Message.success("批量替换成功...")
|
||||||
return true
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Message.error("请在表格中选择行...")
|
Message.error("请在表格中选择行...")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 给时间范围选择器的快捷选择数组
|
||||||
|
const shortcuts = [
|
||||||
|
{
|
||||||
|
label: "后六天",
|
||||||
|
value: () => [dayjs(), dayjs().add(6, "day")]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "前六天",
|
||||||
|
value: () => [dayjs(), dayjs().subtract(6, "day")]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "前后各一周",
|
||||||
|
value: () => [dayjs().subtract(7, "day"), dayjs().add(7, "day")]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
// 其他:打开modal
|
// 其他:打开modal
|
||||||
const open = () => {
|
const open = (getFunc: (() => number[]) | undefined) => {
|
||||||
exetime.value = ""
|
if (getFunc) getSelectedsFunc.value = getFunc
|
||||||
|
exetime.value = []
|
||||||
visible.value = true
|
visible.value = true
|
||||||
}
|
}
|
||||||
defineExpose({ open })
|
defineExpose({ open })
|
||||||
|
|||||||
@@ -56,9 +56,14 @@ const formData = ref({})
|
|||||||
const designPerson = ref("不替换")
|
const designPerson = ref("不替换")
|
||||||
const testPerson = ref("不替换")
|
const testPerson = ref("不替换")
|
||||||
const monitorPerson = ref("不替换")
|
const monitorPerson = ref("不替换")
|
||||||
|
const getSelectedsFunc = ref<any>(() => [])
|
||||||
|
|
||||||
// props
|
// props
|
||||||
const { selectRows } = defineProps<{ selectRows: any }>()
|
const { selectRows } = defineProps<{ selectRows: any }>()
|
||||||
|
|
||||||
|
// emits
|
||||||
|
const emit = defineEmits(["modifySuccess"])
|
||||||
|
|
||||||
// 1.在created时候直接请求后端项目人员信息
|
// 1.在created时候直接请求后端项目人员信息
|
||||||
const persons = ref([])
|
const persons = ref([])
|
||||||
async function fetchPersonByProject() {
|
async function fetchPersonByProject() {
|
||||||
@@ -74,15 +79,17 @@ const submitReplace = async () => {
|
|||||||
Message.error("至少选择一项替换")
|
Message.error("至少选择一项替换")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (selectRows.length && selectRows.length > 0) {
|
const selecteds = selectRows || getSelectedsFunc.value() || []
|
||||||
|
if (selecteds.length && selecteds.length > 0) {
|
||||||
// 判断是否选择了行
|
// 判断是否选择了行
|
||||||
// 请求后台执行
|
// 请求后台执行
|
||||||
await caseApi.personReplace({
|
await caseApi.personReplace({
|
||||||
selectRows: selectRows,
|
selectRows: selecteds,
|
||||||
designPerson: designPerson.value,
|
designPerson: designPerson.value,
|
||||||
testPerson: testPerson.value,
|
testPerson: testPerson.value,
|
||||||
monitorPerson: monitorPerson.value
|
monitorPerson: monitorPerson.value
|
||||||
})
|
})
|
||||||
|
emit("modifySuccess")
|
||||||
Message.success("批量替换成功...")
|
Message.success("批量替换成功...")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -91,7 +98,8 @@ const submitReplace = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 其他:打开modal
|
// 其他:打开modal
|
||||||
const open = () => {
|
const open = (getFunc: (() => number[]) | undefined) => {
|
||||||
|
if (getFunc) getSelectedsFunc.value = getFunc
|
||||||
designPerson.value = "不替换"
|
designPerson.value = "不替换"
|
||||||
testPerson.value = "不替换"
|
testPerson.value = "不替换"
|
||||||
monitorPerson.value = "不替换"
|
monitorPerson.value = "不替换"
|
||||||
|
|||||||
@@ -3,11 +3,7 @@
|
|||||||
<search v-show="searchVisible" @submit="searchSubmit" />
|
<search v-show="searchVisible" @submit="searchSubmit" />
|
||||||
<div class="lg:flex justify-between mb-2">
|
<div class="lg:flex justify-between mb-2">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-popconfirm
|
<a-popconfirm content="确定要删除数据吗? 这会删除全部下级数据!" position="bottom" @ok="deletesMultipleAction">
|
||||||
content="确定要删除数据吗? 这会删除全部下级数据!"
|
|
||||||
position="bottom"
|
|
||||||
@ok="deletesMultipleAction"
|
|
||||||
>
|
|
||||||
<a-button type="primary" status="danger">
|
<a-button type="primary" status="danger">
|
||||||
批量删除
|
批量删除
|
||||||
<template #icon><icon-delete /></template>
|
<template #icon><icon-delete /></template>
|
||||||
@@ -18,8 +14,8 @@
|
|||||||
<template #icon><icon-swap /></template>
|
<template #icon><icon-swap /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-divider direction="vertical"></a-divider>
|
<a-divider direction="vertical"></a-divider>
|
||||||
<a-button type="primary" @click="handlePerpleModal">批量修改人员</a-button>
|
<a-button type="outline" @click="handlePerpleModal">批量修改人员</a-button>
|
||||||
<a-button type="primary" @click="handleExetimeModal">批量修改时间</a-button>
|
<a-button type="outline" @click="handleExetimeModal">批量修改时间</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-space class="lg:mt-0 mt-2">
|
<a-space class="lg:mt-0 mt-2">
|
||||||
@@ -126,7 +122,7 @@
|
|||||||
ref="replaceModal"
|
ref="replaceModal"
|
||||||
:selectRows="selecteds"
|
:selectRows="selecteds"
|
||||||
:api="caseApi.replace"
|
:api="caseApi.replace"
|
||||||
:columns="columns"
|
:columns="columns.filter((it) => it.dataIndex !== 'ident')"
|
||||||
key="modal-case"
|
key="modal-case"
|
||||||
popup-key="case"
|
popup-key="case"
|
||||||
@replaceSuccess="replaceSuccessHandle"
|
@replaceSuccess="replaceSuccessHandle"
|
||||||
@@ -152,8 +148,8 @@ import useDelete from "@/views/project/opeSets/components/DesignTable/useDelete"
|
|||||||
import { Message } from "@arco-design/web-vue"
|
import { Message } from "@arco-design/web-vue"
|
||||||
import ReplaceModal from "@/views/project/opeSets/components/DesignTable/ReplaceModal.vue"
|
import ReplaceModal from "@/views/project/opeSets/components/DesignTable/ReplaceModal.vue"
|
||||||
// case专属组件
|
// case专属组件
|
||||||
import ReplacePerson from "./ReplacePerson.vue"
|
import ReplacePerson from "@/views/project/opeSets/components/CaseTable/ReplacePerson.vue"
|
||||||
import ReplaceExetime from "./ReplaceExetime.vue"
|
import ReplaceExetime from "@/views/project/opeSets/components/CaseTable/ReplaceExetime.vue"
|
||||||
|
|
||||||
const columns = ref([
|
const columns = ref([
|
||||||
{
|
{
|
||||||
@@ -164,7 +160,8 @@ const columns = ref([
|
|||||||
hide: false,
|
hide: false,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
search: true,
|
search: true,
|
||||||
formType: "input"
|
formType: "input",
|
||||||
|
fixed: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "名称",
|
title: "名称",
|
||||||
@@ -174,7 +171,8 @@ const columns = ref([
|
|||||||
hide: false,
|
hide: false,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
search: true,
|
search: true,
|
||||||
formType: "input"
|
formType: "input",
|
||||||
|
fixed: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "用例综述",
|
title: "用例综述",
|
||||||
@@ -186,7 +184,8 @@ const columns = ref([
|
|||||||
search: false, // 不搜索
|
search: false, // 不搜索
|
||||||
formType: "input", // 搜索输入框形式
|
formType: "input", // 搜索输入框形式
|
||||||
isHyperText: false,
|
isHyperText: false,
|
||||||
bodyCellClass: "hyperTextCell-table-chen"
|
bodyCellClass: "hyperTextCell-table-chen",
|
||||||
|
fixed: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 这是单独处理的字段,只声明Search组件相关属性
|
// 这是单独处理的字段,只声明Search组件相关属性
|
||||||
@@ -198,16 +197,14 @@ const columns = ref([
|
|||||||
ellipsis: false,
|
ellipsis: false,
|
||||||
search: false, // 要搜索
|
search: false, // 要搜索
|
||||||
formType: "input", // 搜索输入框形式
|
formType: "input", // 搜索输入框形式
|
||||||
bodyCellClass: "hyperTextCell-table-chen"
|
bodyCellClass: "hyperTextCell-table-chen",
|
||||||
|
fixed: ""
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
provide("columns", columns)
|
provide("columns", columns)
|
||||||
|
|
||||||
// 3.query查询和分页相关
|
// 3.query查询和分页相关
|
||||||
const { tableData, isFetching, fetchData, total, pageChange, pageSizeChange, searchParams } = useFetchData(
|
const { tableData, isFetching, fetchData, total, pageChange, pageSizeChange, searchParams } = useFetchData(caseApi.getCaseList, columns)
|
||||||
caseApi.getCaseList,
|
|
||||||
columns
|
|
||||||
)
|
|
||||||
|
|
||||||
// 4.表单相关
|
// 4.表单相关
|
||||||
const formRef = ref<InstanceType<typeof Form> | null>(null)
|
const formRef = ref<InstanceType<typeof Form> | null>(null)
|
||||||
@@ -224,7 +221,7 @@ const { deletesMultipleAction } = useDelete(caseApi.delete, fetchData, selecteds
|
|||||||
// 7.2.批量替换相关
|
// 7.2.批量替换相关
|
||||||
const replaceModal = ref<InstanceType<typeof ReplaceModal> | null>(null)
|
const replaceModal = ref<InstanceType<typeof ReplaceModal> | null>(null)
|
||||||
const handleOpenReplaceModal = () => {
|
const handleOpenReplaceModal = () => {
|
||||||
replaceModal.value?.open()
|
replaceModal.value?.open(undefined)
|
||||||
}
|
}
|
||||||
const replaceSuccessHandle = async (count: number) => {
|
const replaceSuccessHandle = async (count: number) => {
|
||||||
Message.success(`批量更新成功,尝试更新行数:${count}`)
|
Message.success(`批量更新成功,尝试更新行数:${count}`)
|
||||||
@@ -242,13 +239,13 @@ const searchSubmit = (data: ISearchFormCase) => {
|
|||||||
// 9.批量修改人员
|
// 9.批量修改人员
|
||||||
const replacePersonModalRef = ref<InstanceType<typeof ReplacePerson>>()
|
const replacePersonModalRef = ref<InstanceType<typeof ReplacePerson>>()
|
||||||
const handlePerpleModal = () => {
|
const handlePerpleModal = () => {
|
||||||
replacePersonModalRef.value.open()
|
replacePersonModalRef.value!.open(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10.批量修改事件
|
// 10.批量修改时间
|
||||||
const replaceExetimeModalRef = ref<InstanceType<typeof ReplacePerson>>()
|
const replaceExetimeModalRef = ref<InstanceType<typeof ReplaceExetime>>()
|
||||||
const handleExetimeModal = () => {
|
const handleExetimeModal = () => {
|
||||||
replaceExetimeModalRef.value.open()
|
replaceExetimeModalRef.value!.open(undefined)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<div class="replace-priority-container">
|
||||||
|
<modalDom />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="tsx">
|
||||||
|
import { ref } from "vue"
|
||||||
|
import { Modal, Form, FormItem, Select, Option, Message } from "@arco-design/web-vue"
|
||||||
|
import demandApi from "@/api/project/testDemand"
|
||||||
|
// props
|
||||||
|
const { selectedRows = undefined } = defineProps<{
|
||||||
|
selectedRows?: number[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// refs
|
||||||
|
const visible = ref(false)
|
||||||
|
const formData = ref({})
|
||||||
|
const priority = ref("")
|
||||||
|
const getSelectedsFunc = ref<() => number[]>(() => [])
|
||||||
|
|
||||||
|
// emits
|
||||||
|
const emit = defineEmits(["modifySuccess"])
|
||||||
|
|
||||||
|
// 异步提交修改
|
||||||
|
const submitReplace = async () => {
|
||||||
|
// 不再非受控验证,手动验证
|
||||||
|
if (priority.value === "") {
|
||||||
|
Message.error("请选择优先级后进行提交")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const selecteds = selectedRows || getSelectedsFunc.value() || []
|
||||||
|
if (selecteds.length && selecteds.length > 0) {
|
||||||
|
// 判断是否选择了行
|
||||||
|
// 请求后台执行
|
||||||
|
await demandApi.priorityReplace({
|
||||||
|
selectRows: selecteds as any,
|
||||||
|
priority: priority.value
|
||||||
|
})
|
||||||
|
emit("modifySuccess")
|
||||||
|
Message.success("批量替换成功...")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Message.error("请在表格中选择行...")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹窗主DOM
|
||||||
|
const modalDom = () => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
v-model={[visible.value, "visible"]}
|
||||||
|
width="40%"
|
||||||
|
cancel-text="关闭"
|
||||||
|
ok-text="确认修改"
|
||||||
|
unmount-on-close
|
||||||
|
draggable
|
||||||
|
on-before-ok={submitReplace}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
title: () => "批量修改优先级",
|
||||||
|
default: () => (
|
||||||
|
<div class="content-container">
|
||||||
|
<Form model={formData.value} scroll-to-first-error>
|
||||||
|
<FormItem field="priority" label="优先级">
|
||||||
|
<Select placeholder="请选择要修改成为的优先级" v-model={priority.value}>
|
||||||
|
<Option value="1">高</Option>
|
||||||
|
<Option value="2">中</Option>
|
||||||
|
<Option value="3">低</Option>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// open expose
|
||||||
|
const open = (getFunc: (() => number[]) | undefined) => {
|
||||||
|
if (getFunc) getSelectedsFunc.value = getFunc
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
defineExpose({ open })
|
||||||
|
|
||||||
|
// component options
|
||||||
|
defineOptions({ name: "ReplacePriority" })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
||||||
@@ -27,8 +27,10 @@ import useColumn from "@/views/project/dut/hooks/useColumns"
|
|||||||
|
|
||||||
// refs
|
// refs
|
||||||
const formRef = ref(null)
|
const formRef = ref(null)
|
||||||
|
|
||||||
// 0.props-表示不通用代码
|
// 0.props-表示不通用代码
|
||||||
const { designInfo } = defineProps<{ designInfo: any }>()
|
const { designInfo } = defineProps<{ designInfo: any }>()
|
||||||
|
|
||||||
// 计算属性单独处理
|
// 计算属性单独处理
|
||||||
const designInfoJudge = computed(() => {
|
const designInfoJudge = computed(() => {
|
||||||
return designInfo
|
return designInfo
|
||||||
|
|||||||
@@ -3,11 +3,7 @@
|
|||||||
<search v-show="searchVisible" @submit="searchSubmit" />
|
<search v-show="searchVisible" @submit="searchSubmit" />
|
||||||
<div class="lg:flex justify-between mb-2">
|
<div class="lg:flex justify-between mb-2">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-popconfirm
|
<a-popconfirm content="确定要删除数据吗? 这会删除全部下级数据!" position="bottom" @ok="deletesMultipleAction">
|
||||||
content="确定要删除数据吗? 这会删除全部下级数据!"
|
|
||||||
position="bottom"
|
|
||||||
@ok="deletesMultipleAction"
|
|
||||||
>
|
|
||||||
<a-button type="primary" status="danger">
|
<a-button type="primary" status="danger">
|
||||||
批量删除
|
批量删除
|
||||||
<template #icon><icon-delete /></template>
|
<template #icon><icon-delete /></template>
|
||||||
@@ -17,6 +13,8 @@
|
|||||||
批量替换
|
批量替换
|
||||||
<template #icon><icon-swap /></template>
|
<template #icon><icon-swap /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<a-divider direction="vertical"></a-divider>
|
||||||
|
<a-button type="outline" @click="handleOpenReplacePriority">批量修改优先级</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-space class="lg:mt-0 mt-2">
|
<a-space class="lg:mt-0 mt-2">
|
||||||
@@ -60,11 +58,7 @@
|
|||||||
<template v-for="column in columns" :key="column.dataIndex">
|
<template v-for="column in columns" :key="column.dataIndex">
|
||||||
<template v-if="!column.hide">
|
<template v-if="!column.hide">
|
||||||
<!-- 正常的数据列 -->
|
<!-- 正常的数据列 -->
|
||||||
<a-table-column
|
<a-table-column v-bind="column" v-if="!column.showType && column.dataIndex !== 'testContent'" tooltip>
|
||||||
v-bind="column"
|
|
||||||
v-if="!column.showType && column.dataIndex !== 'testContent'"
|
|
||||||
tooltip
|
|
||||||
>
|
|
||||||
<!-- 如果column有isHyperText属性,则直接渲染html -->
|
<!-- 如果column有isHyperText属性,则直接渲染html -->
|
||||||
<template #cell="{ record }" v-if="column.isHyperText">
|
<template #cell="{ record }" v-if="column.isHyperText">
|
||||||
<div v-html="record[column.dataIndex]"></div>
|
<div v-html="record[column.dataIndex]"></div>
|
||||||
@@ -77,15 +71,12 @@
|
|||||||
<!-- 如果有测试子项即subStep -->
|
<!-- 如果有测试子项即subStep -->
|
||||||
<template v-for="(sub, idx) in record[column.dataIndex]" :key="idx">
|
<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">
|
<template v-for="(step, index) in sub.subStep" :key="index">
|
||||||
<span class="text-amber-700">步骤{{ index + 1 }})</span>
|
<span class="text-amber-700">步骤{{ index + 1 }})</span>
|
||||||
<div class="operation">
|
<div class="operation"><span class="text-bold">操作:</span>{{ step.operation }}</div>
|
||||||
<span class="text-bold">操作:</span>{{ step.operation }}
|
<div class="mb-1"><span class="text-bold">预期:</span>{{ step.expect }}</div>
|
||||||
</div>
|
|
||||||
<div class="mb-1">
|
|
||||||
<span class="text-bold">预期:</span>{{ step.expect }}
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,11 +127,13 @@
|
|||||||
ref="replaceModal"
|
ref="replaceModal"
|
||||||
:selectRows="selecteds"
|
:selectRows="selecteds"
|
||||||
:api="demandApi.replace"
|
:api="demandApi.replace"
|
||||||
:columns="columns"
|
:columns="columns.filter((it) => it.dataIndex !== 'priority' && it.dataIndex !== 'testType')"
|
||||||
key="modal-demand"
|
key="modal-demand"
|
||||||
popup-key="demand"
|
popup-key="demand"
|
||||||
@replaceSuccess="replaceSuccessHandle"
|
@replaceSuccess="replaceSuccessHandle"
|
||||||
/>
|
/>
|
||||||
|
<!-- 批量修改优先级 -->
|
||||||
|
<ReplacePriority ref="replacePriorityRef" @modifySuccess="fetchData()" :selected-rows="selecteds" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -157,6 +150,7 @@ import Trigger from "./Trigger.vue"
|
|||||||
import Search from "@/views/project/opeSets/components/DesignTable/Search.vue"
|
import Search from "@/views/project/opeSets/components/DesignTable/Search.vue"
|
||||||
import useDelete from "@/views/project/opeSets/components/DesignTable/useDelete"
|
import useDelete from "@/views/project/opeSets/components/DesignTable/useDelete"
|
||||||
import ReplaceModal from "@/views/project/opeSets/components/DesignTable/ReplaceModal.vue"
|
import ReplaceModal from "@/views/project/opeSets/components/DesignTable/ReplaceModal.vue"
|
||||||
|
import ReplacePriority from "@/views/project/opeSets/components/DemandTable/ReplacePriority.vue"
|
||||||
import { Message } from "@arco-design/web-vue"
|
import { Message } from "@arco-design/web-vue"
|
||||||
|
|
||||||
// 0.[不同]定义列字段
|
// 0.[不同]定义列字段
|
||||||
@@ -169,7 +163,8 @@ const columns = ref([
|
|||||||
hide: false,
|
hide: false,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
search: true,
|
search: true,
|
||||||
formType: "input"
|
formType: "input",
|
||||||
|
fixed: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "名称",
|
title: "名称",
|
||||||
@@ -179,7 +174,8 @@ const columns = ref([
|
|||||||
hide: false,
|
hide: false,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
search: true,
|
search: true,
|
||||||
formType: "input"
|
formType: "input",
|
||||||
|
fixed: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "优先级",
|
title: "优先级",
|
||||||
@@ -194,7 +190,8 @@ const columns = ref([
|
|||||||
// 只是指明a-table-column渲染v-if
|
// 只是指明a-table-column渲染v-if
|
||||||
showType: (text: string) => {
|
showType: (text: string) => {
|
||||||
return text
|
return text
|
||||||
}
|
},
|
||||||
|
fixed: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "测试类型",
|
title: "测试类型",
|
||||||
@@ -208,7 +205,8 @@ const columns = ref([
|
|||||||
dict: true,
|
dict: true,
|
||||||
showType: (text: string) => {
|
showType: (text: string) => {
|
||||||
return text
|
return text
|
||||||
}
|
},
|
||||||
|
fixed: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "测项描述",
|
title: "测项描述",
|
||||||
@@ -220,7 +218,8 @@ const columns = ref([
|
|||||||
search: false, // 不搜索
|
search: false, // 不搜索
|
||||||
formType: "input", // 搜索输入框形式
|
formType: "input", // 搜索输入框形式
|
||||||
isHyperText: false,
|
isHyperText: false,
|
||||||
bodyCellClass: "hyperTextCell-table-chen"
|
bodyCellClass: "hyperTextCell-table-chen",
|
||||||
|
fixed: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 这是单独处理的字段,只声明Search组件相关属性
|
// 这是单独处理的字段,只声明Search组件相关属性
|
||||||
@@ -232,7 +231,8 @@ const columns = ref([
|
|||||||
ellipsis: false,
|
ellipsis: false,
|
||||||
search: false, // 要搜索
|
search: false, // 要搜索
|
||||||
formType: "input", // 搜索输入框形式
|
formType: "input", // 搜索输入框形式
|
||||||
bodyCellClass: "hyperTextCell-table-chen"
|
bodyCellClass: "hyperTextCell-table-chen",
|
||||||
|
fixed: ""
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
provide("columns", columns)
|
provide("columns", columns)
|
||||||
@@ -242,10 +242,7 @@ const showType = useShowType("priority")
|
|||||||
const showTestType = useShowType("testType")
|
const showTestType = useShowType("testType")
|
||||||
|
|
||||||
// 3.query查询和分页相关
|
// 3.query查询和分页相关
|
||||||
const { tableData, isFetching, fetchData, total, pageChange, pageSizeChange, searchParams } = useFetchData(
|
const { tableData, isFetching, fetchData, total, pageChange, pageSizeChange, searchParams } = useFetchData(demandApi.getTestDemandList, columns)
|
||||||
demandApi.getTestDemandList,
|
|
||||||
columns
|
|
||||||
)
|
|
||||||
|
|
||||||
// 4.表单相关
|
// 4.表单相关
|
||||||
const formRef = ref<InstanceType<typeof Form> | null>(null)
|
const formRef = ref<InstanceType<typeof Form> | null>(null)
|
||||||
@@ -262,7 +259,7 @@ const { deletesMultipleAction } = useDelete(demandApi.delete, fetchData, selecte
|
|||||||
// 7.2.批量替换相关
|
// 7.2.批量替换相关
|
||||||
const replaceModal = ref<InstanceType<typeof ReplaceModal> | null>(null)
|
const replaceModal = ref<InstanceType<typeof ReplaceModal> | null>(null)
|
||||||
const handleOpenReplaceModal = () => {
|
const handleOpenReplaceModal = () => {
|
||||||
replaceModal.value?.open()
|
replaceModal.value?.open(undefined)
|
||||||
}
|
}
|
||||||
const replaceSuccessHandle = async (count: number) => {
|
const replaceSuccessHandle = async (count: number) => {
|
||||||
Message.success(`批量更新成功,尝试更新行数:${count}`)
|
Message.success(`批量更新成功,尝试更新行数:${count}`)
|
||||||
@@ -276,6 +273,12 @@ const searchSubmit = (data: ISearchFormDemand) => {
|
|||||||
searchParams.value = { ...data }
|
searchParams.value = { ...data }
|
||||||
fetchData(true)
|
fetchData(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 9.批量修改优先级-priority
|
||||||
|
const replacePriorityRef = ref<InstanceType<typeof ReplacePriority> | null>(null)
|
||||||
|
const handleOpenReplacePriority = () => {
|
||||||
|
replacePriorityRef.value?.open(undefined)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|||||||
@@ -63,14 +63,21 @@ import { useRoute } from "vue-router"
|
|||||||
// refs
|
// refs
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
// 保存获取选择行的函数
|
||||||
|
const selectedGetFunc = ref<() => number[] | undefined>(() => [])
|
||||||
|
|
||||||
// ref
|
// ref
|
||||||
const formRef = ref<InstanceType<typeof Form>>(null)
|
const formRef = ref<InstanceType<typeof Form>>()
|
||||||
|
|
||||||
// props
|
// props
|
||||||
/// 已选择的行数据的id列表
|
/// 已选择的行数据的id列表
|
||||||
const { selectRows, columns, api, popupKey } = defineProps<{
|
const {
|
||||||
selectRows: number[]
|
selectRows = undefined,
|
||||||
|
columns,
|
||||||
|
api,
|
||||||
|
popupKey
|
||||||
|
} = defineProps<{
|
||||||
|
selectRows?: number[]
|
||||||
columns: any[]
|
columns: any[]
|
||||||
api: Function
|
api: Function
|
||||||
popupKey: string
|
popupKey: string
|
||||||
@@ -94,18 +101,27 @@ const submitReplace = async () => {
|
|||||||
// 验证表单
|
// 验证表单
|
||||||
const validate = await formRef.value.validate()
|
const validate = await formRef.value.validate()
|
||||||
if (!validate) {
|
if (!validate) {
|
||||||
|
// 判断使用selectedRows还是selectedOpenIn
|
||||||
|
let selecteds = selectRows || selectedGetFunc.value!() || []
|
||||||
// 进入这里表示验证通过,手动验证是否选择了行
|
// 进入这里表示验证通过,手动验证是否选择了行
|
||||||
if (selectRows.length < 1) {
|
if (selecteds.length < 1) {
|
||||||
// 提示用户需要先选择table的行
|
// 提示用户需要先选择table的行
|
||||||
Message.error("请先在表格中选择行进行替换")
|
Message.error("您还未选择行...")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// 都验证后提交给后端操作
|
// 都验证后提交给后端操作
|
||||||
try {
|
try {
|
||||||
|
// 首先该组件两个地方使用,需要处理key的问题
|
||||||
|
const round_key = {
|
||||||
|
key: route.query.key as string
|
||||||
|
}
|
||||||
|
if (round_key.key && round_key.key.split("-").length > 1) {
|
||||||
|
round_key.key = round_key.key.split("-")[0]
|
||||||
|
}
|
||||||
const res = await api({
|
const res = await api({
|
||||||
project_id: route.query.id,
|
project_id: route.query.id,
|
||||||
round_key: route.query.key,
|
round_key: round_key.key,
|
||||||
selectRows,
|
selectRows: selecteds,
|
||||||
...formData.value
|
...formData.value
|
||||||
})
|
})
|
||||||
// 批量修改成功放出信号给父组件更新表格
|
// 批量修改成功放出信号给父组件更新表格
|
||||||
@@ -119,7 +135,11 @@ const submitReplace = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// expose functions
|
// expose functions
|
||||||
const open = () => {
|
const open = (selectedIdsGetFunc: (() => number[]) | undefined) => {
|
||||||
|
// 保存获取用户选择行的函数, 可能是undefined
|
||||||
|
if (selectedIdsGetFunc) {
|
||||||
|
selectedGetFunc.value = selectedIdsGetFunc
|
||||||
|
}
|
||||||
// 每次打开初始化表单数据
|
// 每次打开初始化表单数据
|
||||||
formData.value = {
|
formData.value = {
|
||||||
...initialFormData
|
...initialFormData
|
||||||
|
|||||||
@@ -3,11 +3,7 @@
|
|||||||
<search v-show="searchVisible" @submit="searchSubmit" />
|
<search v-show="searchVisible" @submit="searchSubmit" />
|
||||||
<div class="lg:flex justify-between mb-2">
|
<div class="lg:flex justify-between mb-2">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-popconfirm
|
<a-popconfirm content="确定要删除数据吗? 这会删除全部下级数据!" position="bottom" @ok="deletesMultipleAction">
|
||||||
content="确定要删除数据吗? 这会删除全部下级数据!"
|
|
||||||
position="bottom"
|
|
||||||
@ok="deletesMultipleAction"
|
|
||||||
>
|
|
||||||
<a-button type="primary" status="danger">
|
<a-button type="primary" status="danger">
|
||||||
批量删除
|
批量删除
|
||||||
<template #icon><icon-delete /></template>
|
<template #icon><icon-delete /></template>
|
||||||
@@ -140,7 +136,8 @@ const columns = ref([
|
|||||||
hide: false,
|
hide: false,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
search: true,
|
search: true,
|
||||||
formType: "input"
|
formType: "input",
|
||||||
|
fixed: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "标识",
|
title: "标识",
|
||||||
@@ -150,7 +147,8 @@ const columns = ref([
|
|||||||
hide: false,
|
hide: false,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
search: true,
|
search: true,
|
||||||
formType: "input"
|
formType: "input",
|
||||||
|
fixed: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "章节号",
|
title: "章节号",
|
||||||
@@ -160,7 +158,8 @@ const columns = ref([
|
|||||||
hide: false,
|
hide: false,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
search: true,
|
search: true,
|
||||||
formType: "input"
|
formType: "input",
|
||||||
|
fixed: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "类型",
|
title: "类型",
|
||||||
@@ -174,7 +173,8 @@ const columns = ref([
|
|||||||
showType: (text: string) => {
|
showType: (text: string) => {
|
||||||
return text
|
return text
|
||||||
},
|
},
|
||||||
dict: true
|
dict: true,
|
||||||
|
fixed: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "需求描述",
|
title: "需求描述",
|
||||||
@@ -185,7 +185,8 @@ const columns = ref([
|
|||||||
formType: "input",
|
formType: "input",
|
||||||
// 设置内容单元格样式-注意作用与<td>
|
// 设置内容单元格样式-注意作用与<td>
|
||||||
bodyCellClass: "hyperTextCell-table-chen",
|
bodyCellClass: "hyperTextCell-table-chen",
|
||||||
isHyperText: true
|
isHyperText: true,
|
||||||
|
fixed: ""
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
provide("columns", columns)
|
provide("columns", columns)
|
||||||
@@ -194,10 +195,7 @@ provide("columns", columns)
|
|||||||
const showType = useShowType("demandType")
|
const showType = useShowType("demandType")
|
||||||
|
|
||||||
// 3.query查询和分页相关
|
// 3.query查询和分页相关
|
||||||
const { tableData, isFetching, fetchData, total, pageChange, pageSizeChange, searchParams } = useFetchData(
|
const { tableData, isFetching, fetchData, total, pageChange, pageSizeChange, searchParams } = useFetchData(designApi.getDesignDemandList, columns)
|
||||||
designApi.getDesignDemandList,
|
|
||||||
columns
|
|
||||||
)
|
|
||||||
|
|
||||||
// 4.表单相关
|
// 4.表单相关
|
||||||
const formRef = ref<InstanceType<typeof Form> | null>(null)
|
const formRef = ref<InstanceType<typeof Form> | null>(null)
|
||||||
@@ -214,7 +212,7 @@ const { deletesMultipleAction } = useDelete(designApi.delete, fetchData, selecte
|
|||||||
// 7.2.批量替换相关
|
// 7.2.批量替换相关
|
||||||
const replaceModal = ref<InstanceType<typeof ReplaceModal> | null>(null)
|
const replaceModal = ref<InstanceType<typeof ReplaceModal> | null>(null)
|
||||||
const handleOpenReplaceModal = () => {
|
const handleOpenReplaceModal = () => {
|
||||||
replaceModal.value?.open()
|
replaceModal.value?.open(undefined)
|
||||||
}
|
}
|
||||||
const replaceSuccessHandle = async (count: number) => {
|
const replaceSuccessHandle = async (count: number) => {
|
||||||
Message.success(`批量更新成功,尝试更新行数:${count}`)
|
Message.success(`批量更新成功,尝试更新行数:${count}`)
|
||||||
|
|||||||
@@ -20,3 +20,8 @@ export interface ISearchFormDemand {
|
|||||||
export interface ISearchFormCase {
|
export interface ISearchFormCase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 问题单搜索字段
|
||||||
|
export interface ISearchFormProblem {
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div class="problem-form-container">
|
||||||
|
<a-modal
|
||||||
|
width="60%"
|
||||||
|
draggable
|
||||||
|
unmount-on-close
|
||||||
|
ok-text="确认修改"
|
||||||
|
cancel-text="关闭"
|
||||||
|
v-model:visible="visible"
|
||||||
|
title="修改问题单"
|
||||||
|
:on-before-ok="handleSubmit"
|
||||||
|
>
|
||||||
|
<ma-form v-model="formData" :columns="columns" :options="options" ref="formRef">
|
||||||
|
<template #form-ident> PT_{{ route.query.ident }}_{{ formData["ident"] }} </template>
|
||||||
|
</ma-form>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import useFormColumns from "./formHooks/useFormColumns"
|
||||||
|
import { ref } from "vue"
|
||||||
|
import MaForm from "@/components/ma-form/index.vue"
|
||||||
|
import problemApi from "@/api/project/problem"
|
||||||
|
import { useRoute } from "vue-router"
|
||||||
|
import { Message } from "@arco-design/web-vue"
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const formRef = ref<InstanceType<typeof MaForm> | null>(null)
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
// form数据
|
||||||
|
const project_id = route.query.id
|
||||||
|
const formData = ref<any>({})
|
||||||
|
|
||||||
|
const emit = defineEmits(["updateProblem"])
|
||||||
|
|
||||||
|
// modal提交按钮
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const isValidated = await formRef.value!.validateForm()
|
||||||
|
if (isValidated) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await problemApi.update(formData.value.id, { project_id, ...formData.value })
|
||||||
|
emit("updateProblem")
|
||||||
|
Message.success("修改成功")
|
||||||
|
formData.value = {} // 清除已有数据,防止卡顿
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// options
|
||||||
|
const options = ref({
|
||||||
|
showButtons: false,
|
||||||
|
labelAlign: "center"
|
||||||
|
})
|
||||||
|
|
||||||
|
// columns
|
||||||
|
const { columns } = useFormColumns()
|
||||||
|
|
||||||
|
const open = (record: any) => {
|
||||||
|
visible.value = true
|
||||||
|
formData.value = record
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
import { useRoute } from "vue-router"
|
||||||
|
import { useUserStore } from "@/store"
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const route = useRoute()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "id",
|
||||||
|
dataIndex: "id",
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "grid",
|
||||||
|
customClass: ["mt-0"],
|
||||||
|
cols: [
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "名称",
|
||||||
|
align: "left",
|
||||||
|
search: true,
|
||||||
|
dataIndex: "name",
|
||||||
|
rules: [{ required: true, message: "名称是必填" }],
|
||||||
|
validateTrigger: "blur-sm"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "问题标识",
|
||||||
|
align: "center",
|
||||||
|
width: 140,
|
||||||
|
search: true,
|
||||||
|
disabled: true,
|
||||||
|
dataIndex: "ident",
|
||||||
|
validateTrigger: "blur-sm"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "grid",
|
||||||
|
customClass: ["ml-4"],
|
||||||
|
cols: [
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "缺陷状态",
|
||||||
|
align: "center",
|
||||||
|
width: 80,
|
||||||
|
search: true,
|
||||||
|
dataIndex: "status",
|
||||||
|
formType: "radio",
|
||||||
|
rules: [{ required: true, message: "缺陷状态是必填" }],
|
||||||
|
dict: {
|
||||||
|
name: "problemStatu",
|
||||||
|
translation: true,
|
||||||
|
props: { label: "title", value: "key" },
|
||||||
|
tagColors: { 1: "green", 2: "blue", 3: "#FF7D00", 4: "red" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "闭环方式",
|
||||||
|
align: "center",
|
||||||
|
width: 200,
|
||||||
|
dataIndex: "closeMethod",
|
||||||
|
search: true,
|
||||||
|
formType: "checkbox",
|
||||||
|
dict: {
|
||||||
|
name: "closeMethod",
|
||||||
|
translation: true,
|
||||||
|
props: { label: "title", value: "key" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "grid",
|
||||||
|
customClass: ["ml-4"],
|
||||||
|
cols: [
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "缺陷等级",
|
||||||
|
align: "center",
|
||||||
|
width: 80,
|
||||||
|
dataIndex: "grade",
|
||||||
|
search: true,
|
||||||
|
formType: "radio",
|
||||||
|
rules: [{ required: true, message: "缺陷等级必填" }],
|
||||||
|
dict: {
|
||||||
|
name: "problemGrade",
|
||||||
|
translation: true,
|
||||||
|
props: { label: "title", value: "key" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "缺陷类型",
|
||||||
|
align: "center",
|
||||||
|
width: 80,
|
||||||
|
dataIndex: "type",
|
||||||
|
search: true,
|
||||||
|
formType: "radio",
|
||||||
|
rules: [{ required: true, message: "缺陷类型必选" }],
|
||||||
|
dict: {
|
||||||
|
name: "problemType",
|
||||||
|
translation: true,
|
||||||
|
props: { label: "title", value: "key" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "divider"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "grid",
|
||||||
|
cols: [
|
||||||
|
{
|
||||||
|
span: 24,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "问题描述",
|
||||||
|
hide: true,
|
||||||
|
search: true,
|
||||||
|
align: "center",
|
||||||
|
dataIndex: "operation",
|
||||||
|
formType: "editor"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "grid",
|
||||||
|
cols: [
|
||||||
|
{
|
||||||
|
span: 24,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "问题影响",
|
||||||
|
hide: true,
|
||||||
|
align: "center",
|
||||||
|
dataIndex: "result",
|
||||||
|
formType: "textarea"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "divider"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "grid",
|
||||||
|
cols: [
|
||||||
|
{
|
||||||
|
span: 24,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "原因分析",
|
||||||
|
hide: true,
|
||||||
|
align: "center",
|
||||||
|
dataIndex: "analysis",
|
||||||
|
formType: "textarea"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "grid",
|
||||||
|
cols: [
|
||||||
|
{
|
||||||
|
span: 24,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "影响域分析",
|
||||||
|
hide: true,
|
||||||
|
align: "center",
|
||||||
|
dataIndex: "effect_scope",
|
||||||
|
formType: "textarea"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "grid",
|
||||||
|
cols: [
|
||||||
|
{
|
||||||
|
span: 24,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "改正措施",
|
||||||
|
hide: true,
|
||||||
|
align: "center",
|
||||||
|
dataIndex: "solve",
|
||||||
|
formType: "textarea"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "divider"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "grid",
|
||||||
|
cols: [
|
||||||
|
{
|
||||||
|
span: 24,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "回归结果",
|
||||||
|
hide: true,
|
||||||
|
align: "center",
|
||||||
|
dataIndex: "verify_result",
|
||||||
|
formType: "editor"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "divider",
|
||||||
|
title: "人员信息"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "grid",
|
||||||
|
cols: [
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "测试人员",
|
||||||
|
dataIndex: "postPerson",
|
||||||
|
search: true,
|
||||||
|
align: "center",
|
||||||
|
formType: "select",
|
||||||
|
rules: [{ required: true, message: "测试人员必填" }],
|
||||||
|
dict: {
|
||||||
|
url: "system/user/list",
|
||||||
|
params: { project_id: route.query.id },
|
||||||
|
translation: true,
|
||||||
|
props: { label: "name", value: "name" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "测试日期",
|
||||||
|
hide: true,
|
||||||
|
dataIndex: "postDate",
|
||||||
|
formType: "date"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "grid",
|
||||||
|
cols: [
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "开发人员",
|
||||||
|
hide: true,
|
||||||
|
dataIndex: "designerPerson",
|
||||||
|
formType: "input"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "开发方日期",
|
||||||
|
hide: true,
|
||||||
|
dataIndex: "designDate",
|
||||||
|
formType: "date"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formType: "grid",
|
||||||
|
cols: [
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "回归人员",
|
||||||
|
hide: true,
|
||||||
|
dataIndex: "verifyPerson",
|
||||||
|
formType: "select",
|
||||||
|
dict: {
|
||||||
|
url: "system/user/list",
|
||||||
|
params: { project_id: route.query.id },
|
||||||
|
translation: true,
|
||||||
|
props: { label: "name", value: "name" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
formList: [
|
||||||
|
{
|
||||||
|
title: "回归日期",
|
||||||
|
hide: true,
|
||||||
|
dataIndex: "verifyDate",
|
||||||
|
formType: "date"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return { columns }
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { ref } from "vue"
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const columns = ref([
|
||||||
|
{
|
||||||
|
title: "名称",
|
||||||
|
dataIndex: "name",
|
||||||
|
slotName: "name",
|
||||||
|
align: "left",
|
||||||
|
hide: false,
|
||||||
|
width: 250,
|
||||||
|
fixed: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "问题标识",
|
||||||
|
dataIndex: "ident",
|
||||||
|
slotName: "ident",
|
||||||
|
align: "center",
|
||||||
|
hide: false,
|
||||||
|
width: 150,
|
||||||
|
fixed: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "缺陷状态",
|
||||||
|
dataIndex: "problemStatu",
|
||||||
|
slotName: "problemStatu",
|
||||||
|
align: "center",
|
||||||
|
hide: false,
|
||||||
|
width: 100,
|
||||||
|
fixed: "",
|
||||||
|
formType: "select",
|
||||||
|
dict: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "缺陷等级",
|
||||||
|
dataIndex: "problemGrade",
|
||||||
|
slotName: "problemGrade",
|
||||||
|
align: "center",
|
||||||
|
hide: false,
|
||||||
|
width: 100,
|
||||||
|
fixed: "",
|
||||||
|
formType: "select",
|
||||||
|
dict: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "问题描述",
|
||||||
|
dataIndex: "operation",
|
||||||
|
slotName: "operation",
|
||||||
|
align: "left",
|
||||||
|
hide: false,
|
||||||
|
isHyperText: true,
|
||||||
|
fixed: ""
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
return { columns }
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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 gradeOptions = computed(() => {
|
||||||
|
return Object.fromEntries(originOption.value.map(({ title, key }) => [key, title]))
|
||||||
|
})
|
||||||
|
// 请求字典中测试手段数据
|
||||||
|
const fetchDictData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await dictApi.getDictByCode({ code: "problemGrade" })
|
||||||
|
originOption.value = res.data
|
||||||
|
loading.value = true
|
||||||
|
} catch (e) {
|
||||||
|
Message.error("获取测试手段选项失败,请关闭后重新打开!")
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 一个工具函数
|
||||||
|
const changeGradeColor = (status: "1" | "2" | "3" | "4") => {
|
||||||
|
const colorDict = {
|
||||||
|
"1": "blue",
|
||||||
|
"2": "orange",
|
||||||
|
"3": "green",
|
||||||
|
"4": "red"
|
||||||
|
}
|
||||||
|
return colorDict[status]
|
||||||
|
}
|
||||||
|
fetchDictData()
|
||||||
|
return { fetchDictData, loading, gradeOptions, changeGradeColor }
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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 problemStatus = computed(() => {
|
||||||
|
return Object.fromEntries(originOption.value.map(({ title, key }) => [key, title]))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 请求字典中测试手段数据
|
||||||
|
const fetchDictData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await dictApi.getDictByCode({ code: "problemStatu" })
|
||||||
|
originOption.value = res.data
|
||||||
|
loading.value = true
|
||||||
|
} catch (e) {
|
||||||
|
Message.error("获取测试手段选项失败,请关闭后重新打开!")
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 一个工具函数
|
||||||
|
const changeColor = (status: "1" | "2" | "3" | "4") => {
|
||||||
|
const colorDict = {
|
||||||
|
"1": "green",
|
||||||
|
"2": "red",
|
||||||
|
"3": "magenta",
|
||||||
|
"4": "arcoblue"
|
||||||
|
}
|
||||||
|
return colorDict[status]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在组件挂载请求dict
|
||||||
|
fetchDictData()
|
||||||
|
|
||||||
|
return { fetchDictData, loading, problemStatus, changeColor }
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<div class="problem-table-container">
|
||||||
|
<search v-show="searchVisible" @submit="searchSubmit" />
|
||||||
|
<div class="lg:flex justify-between mb-2">
|
||||||
|
<a-space> </a-space>
|
||||||
|
<a-space>
|
||||||
|
<a-space class="lg:mt-0 mt-2">
|
||||||
|
<slot name="tools"></slot>
|
||||||
|
<a-tooltip content="刷新表格">
|
||||||
|
<a-button shape="circle" @click="fetchData()"><icon-refresh /></a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip content="显隐搜索" @click="searchVisible = !searchVisible">
|
||||||
|
<a-button shape="circle"><icon-search /></a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip content="设置">
|
||||||
|
<a-button shape="circle" @click="clickSetting"><icon-settings /></a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
<a-table
|
||||||
|
id="basic-table-problem"
|
||||||
|
v-bind="options"
|
||||||
|
hoverable
|
||||||
|
column-resizable
|
||||||
|
ref="tableRef"
|
||||||
|
row-key="id"
|
||||||
|
:columns="columns"
|
||||||
|
:loading="isFetching || problemStatusLoading || problemGradeLoading"
|
||||||
|
:data="tableData"
|
||||||
|
:scroll="{ x: '100%' }"
|
||||||
|
:pagination="{
|
||||||
|
showTotal: true,
|
||||||
|
showPageSize: true,
|
||||||
|
total: total,
|
||||||
|
pageSizeOptions: [10, 20, 50, 1000],
|
||||||
|
hideOnSinglePage: false
|
||||||
|
}"
|
||||||
|
@page-change="pageChange"
|
||||||
|
@page-size-change="pageSizeChange"
|
||||||
|
>
|
||||||
|
<template #columns>
|
||||||
|
<template v-for="column in columns" :key="column.dataIndex">
|
||||||
|
<template v-if="!column.hide">
|
||||||
|
<a-table-column v-bind="column" v-if="column.dataIndex === 'operation'">
|
||||||
|
<template #cell="{ record }" v-if="column.isHyperText">
|
||||||
|
<div v-html="record[column.dataIndex]"></div>
|
||||||
|
</template>
|
||||||
|
</a-table-column>
|
||||||
|
<a-table-column v-bind="column" v-else tooltip>
|
||||||
|
<template #cell="{ record }">
|
||||||
|
<template v-if="column.dataIndex === 'ident'"> PT_{{ route.query.ident }}_{{ record.ident }} </template>
|
||||||
|
<template v-else-if="column.dataIndex === 'problemStatu'">
|
||||||
|
<a-tag :color="changeColor(record.status)">
|
||||||
|
{{ problemStatus[record.status] }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.dataIndex === 'problemGrade'">
|
||||||
|
<a-tag :color="changeGradeColor(record.grade)">
|
||||||
|
{{ gradeOptions[record.grade] }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ record[column.dataIndex] }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table-column>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<!-- 所属用例列 -->
|
||||||
|
<a-table-column title="所属用例" align="center" :width="110" fixed="right">
|
||||||
|
<template #cell="{ record }">
|
||||||
|
<a-tag v-if="record.hang" color="red">无关联用例</a-tag>
|
||||||
|
<a-button v-else color="green" type="primary" @click="seeRelatedCases(record)">
|
||||||
|
<icon-eye />
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-table-column>
|
||||||
|
<!-- 操作列 -->
|
||||||
|
<a-table-column title="操作" align="center" :width="120" fixed="right">
|
||||||
|
<template #cell="{ record }">
|
||||||
|
<a-link @click="handleOpenProblemModify(record)">
|
||||||
|
修改
|
||||||
|
<template #icon>
|
||||||
|
<icon-edit />
|
||||||
|
</template>
|
||||||
|
</a-link>
|
||||||
|
</template>
|
||||||
|
</a-table-column>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<!-- 问题单表单页面 -->
|
||||||
|
<ProblemForm ref="problemRef" @updateProblem="refresh()" />
|
||||||
|
<!-- 表格设置 -->
|
||||||
|
<my-setting ref="settingRef" @onChangeColumnHide="changeColumn"></my-setting>
|
||||||
|
<case-modal ref="caseModalRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { provide, ref } from "vue"
|
||||||
|
import useColumns from "./hooks/useColumns"
|
||||||
|
import useFetchData from "@/views/project/opeSets/hooks/useFetchData"
|
||||||
|
import useSettings from "@/views/project/opeSets/components/DesignTable/useSettings"
|
||||||
|
import problemApi from "@/api/project/problem"
|
||||||
|
import { useRoute } from "vue-router"
|
||||||
|
import useProblemStatu from "./hooks/useProblemStatu"
|
||||||
|
import useProblemGrade from "./hooks/useProblemGrade"
|
||||||
|
import problemSingleApi from "@/api/project/singleProblem"
|
||||||
|
import CaseModal from "@/views/project/case/components/CaseModal.vue"
|
||||||
|
import MySetting from "@/views/project/opeSets/components/TableCommonComponent/Setting.vue"
|
||||||
|
import Search from "@/views/project/opeSets/components/DesignTable/Search.vue"
|
||||||
|
import ProblemForm from "./form.vue"
|
||||||
|
import type { ISearchFormProblem } from "../DesignTable/types"
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const caseModalRef = ref<InstanceType<typeof CaseModal> | null>(null)
|
||||||
|
const problemRef = ref<InstanceType<typeof ProblemForm> | null>(null)
|
||||||
|
|
||||||
|
// 加载初始字典数据
|
||||||
|
const { loading: problemStatusLoading, problemStatus, changeColor } = useProblemStatu()
|
||||||
|
const { loading: problemGradeLoading, gradeOptions, changeGradeColor } = useProblemGrade()
|
||||||
|
|
||||||
|
// 定义表格列
|
||||||
|
const { columns } = useColumns()
|
||||||
|
provide("columns", columns)
|
||||||
|
|
||||||
|
// 请求初始数据钩子
|
||||||
|
const { tableData, isFetching, fetchData, total, pageChange, pageSizeChange, searchParams } = useFetchData(problemApi.searchAllProblem, columns)
|
||||||
|
|
||||||
|
// 查看关联用例
|
||||||
|
const seeRelatedCases = async (record: any) => {
|
||||||
|
const problemId = record.id
|
||||||
|
const res = await problemSingleApi.getRelativeCases({ id: problemId })
|
||||||
|
caseModalRef.value && caseModalRef.value.open(res.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格设置相关
|
||||||
|
const { options, clickSetting, changeColumn, settingRef } = useSettings()
|
||||||
|
|
||||||
|
// 搜索相关内容
|
||||||
|
const searchVisible = ref(true)
|
||||||
|
const searchSubmit = (data: ISearchFormProblem) => {
|
||||||
|
searchParams.value = { ...data }
|
||||||
|
fetchData(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击修改
|
||||||
|
const handleOpenProblemModify = (record: any) => {
|
||||||
|
problemRef.value?.open(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新表格显示
|
||||||
|
const refresh = () => {
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.max-height-class {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user