文档片段全面改进,不使用render

This commit is contained in:
2025-04-20 17:50:07 +08:00
parent e43f9230eb
commit 68c93f5d83
48 changed files with 1330 additions and 732 deletions

View File

@@ -1,4 +1,6 @@
import { Ref, computed } from "vue"
import { storeToRefs } from "pinia"
import { useAppStore } from "@/store"
// 单个每月项目数量对象格式
interface IData {
@@ -11,6 +13,9 @@ interface ResData {
}
function useVueDataUI(data: Ref<ResData>) {
const appStore = useAppStore()
const { theme } = storeToRefs(appStore)
// 结构pinia储存的主体响应式变量
const initialData = [
{
name: "项目数量",
@@ -29,8 +34,10 @@ function useVueDataUI(data: Ref<ResData>) {
}
return initialData
})
// 暗黑模式识别(这是存在pinia的)
const darkMode = document.body.getAttribute("arco-theme")
const initialConfig = {
theme: "",
theme: darkMode === "dark" ? "celebrationNight" : "",
responsive: false,
customPalette: [],
downsample: { threshold: 500 },
@@ -108,7 +115,7 @@ function useVueDataUI(data: Ref<ResData>) {
fontSize: 18,
bold: true,
textAlign: "left",
paddingLeft: 0,
paddingLeft: 5,
paddingRight: 0,
subtitle: { color: "#CCCCCCff", text: "", fontSize: 16, bold: false },
show: true
@@ -168,7 +175,10 @@ function useVueDataUI(data: Ref<ResData>) {
const countData = data.value.data.map((it) => it.count)
initialConfig.chart.grid.labels.yAxis.scaleMax = Math.max(...countData) ? Math.max(...countData) : 10
}
return initialConfig
return {
...initialConfig,
theme: theme.value === "dark" ? "celebrationNight" : ""
}
})
return { chartData, chartConfig }
}

View File

@@ -26,7 +26,7 @@
<div class="text-center leading-[32px]">管理平台版本:</div>
</div>
<div class="mt-2 leading-[32px]">
<a-tag class="w-fit h-[32px]" color="#0fc6c2">TestPlant V0.0.4</a-tag>
<a-tag class="w-fit h-[32px]" color="#0fc6c2">TestPlant v{{ $version }}</a-tag>
</div>
</div>
</div>

View File

@@ -105,10 +105,10 @@ onMounted(async () => {
width: 75px;
color: #fff;
text-align: center;
line-height: 65px;
font-weight: bold;
font-size: 1.3em;
border-radius: 2px 0 0 2px;
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@@ -6,7 +6,7 @@
<img class="w-[200px] h-[300px]" src="@/assets/img/ErrorLoad.svg" alt="" />
</template>
<template v-else>
<VueUiXy :dataset="chartData" :config="chartConfig" />
<VueUiXy :dataset="chartData" :config="chartConfig" :style="{ padding: '10px' }" />
</template>
</div>
</a-spin>
@@ -18,12 +18,14 @@ import commonApi from "@/api/common"
import { VueUiXy } from "vue-data-ui"
import useVueDataUI from "@/views/dashboard/workplace/components/cpns/hooks/vueDataUI"
import { useQuery } from "@tanstack/vue-query"
// vue-query请求图表接口
const { isPending, data, isError } = useQuery({
queryKey: ["chart"],
queryFn: commonApi.getChartData,
refetchOnWindowFocus: false
})
// vue-data-ui图表
const { chartData, chartConfig } = useVueDataUI(data)
</script>

View File

@@ -108,7 +108,6 @@ import { useUserStore } from "@/store"
import { useRouter, useRoute } from "vue-router"
import userApi from "@/api/system/user"
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
// 绑定登录form的数据
// const form = reactive({ username: "superAdmin", password: "admin123", code: "" })

View File

@@ -1,9 +1,11 @@
import { defineComponent } from "vue"
import { defineComponent, ref } from "vue"
import { TreeNodeData } from "@arco-design/web-vue"
import { useTreeDataStore } from "@/store"
import testDemandAPI from "@/api/project/testDemand"
import useOptions from "./useOptions"
import subFormHooks from "@/views/project/projPublicHooks/subFormHooks"
import useBeforeCancel from "@/views/project/projPublicHooks/useBeforeCancel"
import { cloneDeep } from "lodash-es"
const DemandSubForm = defineComponent({
name: "DemandSubFormForm",
@@ -25,6 +27,8 @@ const DemandSubForm = defineComponent({
// 设置表单名称
title.value = nodeData.title!
const res = await testDemandAPI.getTestDemandOne({ project_id, key }) // **API变化**
// 得到数据时候将beforeFormContent搞定
beforeFormContent.value = cloneDeep(res.data.testContent)
// 更新表单
formData.value = res.data // **属性变化**
formData.value.round = key.split("-")[0]
@@ -38,10 +42,13 @@ const DemandSubForm = defineComponent({
// out use
expose({ open })
// hook-判断是否更变内容关闭-只用于测试项和测试用例
const beforeFormContent = ref<any>(undefined)
const { handleBeforeCancel } = useBeforeCancel(formData, beforeFormContent, visible)
// Dom
return () => (
// 注意v-model:visible是不能放在对象解构的
<a-modal {...modalOptions} v-model:visible={visible.value}>
<a-modal {...modalOptions} v-model:visible={visible.value} on-before-cancel={handleBeforeCancel}>
{{
title: () => <span>[]-{title.value}</span>,
default: () => (

View File

@@ -58,7 +58,7 @@ export default function (crudOrFormRef: any) {
dict: { name: "testType", translation: true, props: { label: "title", value: "key" } },
extra: "支持拼音搜索例如gn可以搜索出功能测试",
// 这是arco的属性所以在ma-crud和ma-form可以直接使用arco属性和事件事件+onXXX
filterOption: function (inputValue, selectedOption) {
filterOption: function (inputValue: any, selectedOption: any) {
if (inputValue) {
let matchRes = PinYinMatch.match(selectedOption.label, inputValue)
if (matchRes) {
@@ -91,7 +91,7 @@ export default function (crudOrFormRef: any) {
dataIndex: "testDesciption",
formType: "textarea",
maxLength: 256,
placeholder: "FPGA-老版本需填写!!!"
placeholder: "请填写整体测试项的描述"
},
{
title: "测试子项",
@@ -99,53 +99,28 @@ export default function (crudOrFormRef: any) {
dataIndex: "testContent",
commonRules: [{ required: true, message: "测试方法是必填的" }],
formType: "children-form",
type: "group",
formList: [
{
title: "子项名称",
dataIndex: "subName",
placeholder: "对应测试项描述标题,和测试方法的标题",
rules: [{ required: true, message: "测试子项名称必填" }],
onChange: (ev) => {
onChange: (ev: any) => {
// 取出子项的对象数组
const subItemFormData = crudOrFormRef.value.getFormData().testContent
// 取出充分性条件字段字符串
const mapRes = subItemFormData.map((subItem) => subItem.subName)
const mapRes = subItemFormData.map((subItem: any) => subItem.subName)
crudOrFormRef.value.getFormData().adequacy = `测试用例覆盖${mapRes.join(
"、"
)}子项要求的全部内容。\n所有用例执行完毕对于未执行的用例说明未执行原因。`
}
},
{
title: "子项描述",
dataIndex: "subDesc",
formType: "textarea",
placeholder: "对应大纲测试项表格的测试项描述FPGA-老模版不用填写!!!"
// rules: [{ required: true, message: "测试子项描述必填" }]
title: "操作与预期",
dataIndex: "subStep",
formType: "steptable"
},
{
title: "条件",
dataIndex: "condition",
formType: "textarea",
placeholder: "在什么环境和前置条件下"
},
{
title: "操作",
dataIndex: "operation",
formType: "textarea",
placeholder: "通过xxx操作"
},
{
title: "观察",
dataIndex: "observe",
formType: "textarea",
placeholder: "查看xxx内容"
},
{
title: "期望",
dataIndex: "expect",
formType: "textarea",
placeholder: "xxx结果正确"
}
]
}
])

View File

@@ -0,0 +1,32 @@
import { isEqual } from "lodash-es"
import { getCurrentInstance } from "vue"
/**
* 该hook为测试项子项和测试用例步骤设计当其改变时候弹窗通知用户
*/
export default function useBeforeCancel(formData: any, beforeFormContent: any, visible: any) {
const app = getCurrentInstance()!.appContext.config.globalProperties
const handleBeforeCancel = () => {
if (!beforeFormContent.value) {
return true
}
const content = formData.value.testContent || formData.value.testStep
const iuEqualValue = isEqual(content, beforeFormContent.value)
!iuEqualValue &&
app.$modal.confirm({
title: "测试项步骤内容你已改动,是否保留您编写的测试项/测试用例步骤数据?",
content: "",
okText: "返回重新编辑",
cancelText: "取消",
simple: true,
onOk: () => {
visible.value = true
},
onCancel: () => {}
})
return true
}
return {
handleBeforeCancel
}
}

View File

@@ -1,9 +1,11 @@
import { defineComponent } from "vue"
import { defineComponent, ref } from "vue"
import { Message, TreeNodeData } from "@arco-design/web-vue"
import { useTreeDataStore } from "@/store"
import caseApi from "@/api/project/case"
import useOptions from "./useOptions"
import subFormHooks from "@/views/project/projPublicHooks/subFormHooks"
import useBeforeCancel from "@/views/project/projPublicHooks/useBeforeCancel"
import { cloneDeep } from "lodash-es"
const CaseSubForm = defineComponent({
name: "DemandSubFormForm",
@@ -26,6 +28,8 @@ const CaseSubForm = defineComponent({
title.value = nodeData.title!
// 注意这里因为case接口原因这里需要projectId!!!!!!!!!!!!!!!
const res = await caseApi.getCaseOne({ projectId: project_id, key }) // **API变化**
// 得到数据时候将beforeFormContent搞定
beforeFormContent.value = cloneDeep(res.data.testStep)
// 更新表单
formData.value = res.data // **属性变化**
formData.value.round = key.split("-")[0]
@@ -41,10 +45,14 @@ const CaseSubForm = defineComponent({
// out use
expose({ open })
// hook-判断是否更变内容关闭-只用于测试项和测试用例
const beforeFormContent = ref<any>(undefined)
const { handleBeforeCancel } = useBeforeCancel(formData, beforeFormContent, visible)
// Dom
return () => (
// 注意v-model:visible是不能放在对象解构的
<a-modal {...modalOptions} v-model:visible={visible.value}>
<a-modal {...modalOptions} v-model:visible={visible.value} on-before-cancel={handleBeforeCancel}>
{{
title: () => <span>[]-{title.value}</span>,
default: () => (

View File

@@ -157,7 +157,7 @@ export default function (crudOrFormRef: any, problemFormRef?: any) {
}
],
formType: "children-form",
type: "group",
type: "group", // 注意这里可能改样式"group"/"table"
formList: [
{
title: "操作",

View File

@@ -0,0 +1,74 @@
<template>
<div class="hg-doc-upload-container">
<div v-if="roundNum === 0">
<a-alert type="warning">暂无回归测试轮次信息</a-alert>
</div>
<a-tabs v-else :default-active-key="1" type="line">
<a-tab-pane v-for="n in roundNum" :key="n" :title="`第${n + 1}轮${documentType}`">
<a-upload
:action="`/api/documentUpload/file?id=${projectId}&documentType=${documentType}&round_num=${n + 1}`"
:limit="1"
accept=".docx, application/vnd.openxmlformats-officedocument.wordprocessingml.document"
@error="uploadErrorHandle"
@before-upload="confirmUploadHandle"
>
<template #upload-button>
<a-button type="primary"><icon-upload />点击上传</a-button>
</template>
</a-upload>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script setup lang="ts">
/* 该组件是回归测试说明、回归测试记录上传逻辑组件 */
import { onMounted, ref } from "vue"
import otherApi from "@/api/generate/other"
import { Message, Modal } from "@arco-design/web-vue"
// props
const { projectId, documentType } = defineProps<{ projectId: number | null; documentType: string }>()
// 组件加载时候就请求后端,有几个轮次
onMounted(async () => {
try {
if (!projectId) return
const {
data: { count }
} = await otherApi.getHgRoundNumber({ id: projectId })
// count为非第一轮其他轮次数量
roundNum.value = count
} catch (err) {
roundNum.value = 0
}
})
// ref - 定义非第一轮轮次数量
const roundNum = ref(0)
// 上传失败的事件 - 报错内容由后端定义message字段
const uploadErrorHandle = (fielItem: any) => {
const res = JSON.parse(fielItem.response)
if (res.message) {
Message.error(res.message)
}
}
// 上传弹窗事件
const confirmUploadHandle = (file: any) => {
return new Promise((resolve, reject) => {
Modal.confirm({
title: "请确认您上传的文件是带有文档片段的docx文档",
content: `${file.name}`,
onOk: () => resolve(true),
onCancel: () => reject("cancel")
})
})
}
defineOptions({
name: "HgDocUpload"
})
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,283 @@
<template>
<!-- 该组件是点击生成的弹出框 -->
<div class="create-modal-container">
<a-modal v-model:visible="visible" unmount-on-close render-to-body width="50%" hide-cancel :footer="false">
<template #title> {{ title }}上传下载 </template>
<a-list class="alist">
<template #header>上传您的{{ title }}</template>
<a-list-item>
<!-- 文件上传回归测试说明回归测试记录 -->
<div v-if="title === '回归测试说明' || title === '回归测试记录'">
<HgDocUpload :project-id="projectId" :document-type="title" />
</div>
<!-- 文件上传其他文档 -->
<div v-else>
<a-upload
:action="`/api/documentUpload/file?id=${projectId}&documentType=${title}`"
:limit="1"
accept=".docx, application/vnd.openxmlformats-officedocument.wordprocessingml.document"
@error="uploadErrorHandle"
@before-upload="confirmUploadHandle"
/>
</div>
</a-list-item>
</a-list>
<!-- 文档片段覆盖选取 -->
<div v-if="fragmentList.length">
<a-list :loading="fragmentListPending" hoverable size="small">
<template #header>选取需要被覆盖的文档片段</template>
<a-list-item>
<div class="buttonAndAlert">
<a-alert type="warning">首次生成请全勾选</a-alert>
<div class="button-list">
<a-button type="primary" status="success" @click="allCheckBtn">全勾选</a-button>
<a-button type="primary" status="warning" @click="allUnCheckBtn">全不勾选</a-button>
</div>
</div>
</a-list-item>
<a-list-item v-for="(frag, index) in fragmentList" :key="index">
<div class="fragment-item">
<div class="fragment-name">{{ frag.name }}</div>
<a-divider direction="vertical" />
<a-switch v-model="frag.isCover"></a-switch>
</div>
</a-list-item>
</a-list>
</div>
<div v-else>
<a-list :loading="fragmentListPending" hoverable size="small">
<template #header>选取需要被覆盖的文档片段</template>
<a-list-item>
<Empty text="未找到文档片段,请先下载" />
</a-list-item>
</a-list>
</div>
<div>
<a-list class="alist">
<a-list-item>
<div class="button-list">
<a-button :loading="isGenerating" type="primary" @click="downloadHandle">
确认并下载
</a-button>
</div>
</a-list-item>
</a-list>
</div>
</a-modal>
<Progress
:visible="progressVisible"
:isComplete="isComplete"
:text="title"
@clickConfirm="handleModalConfirmClick"
></Progress>
</div>
</template>
<script setup lang="ts">
import { DocumentType } from "@/utils/types/CommonType" // 产品文档类型
import { ref } from "vue"
import fragmentApi from "@/api/generate/fragment" // 获取某项目某测试文档的片段api
import Empty from "@/components/Empty/index.vue" // 空状态组件
import HgDocUpload from "@/views/testmanage/projmanage/GeneratorModal/HgDocUpload/index.vue"
import Progress from "@/views/testmanage/projmanage/cpns/progress.vue"
import { Message, Modal } from "@arco-design/web-vue"
import useGenerateSecond from "../hooks/useGenerateSecond"
import useSeitaiModal from "../hooks/useSeitaiModal"
// 定义覆盖文档片段每项类型
export interface IFragmentItem {
name: string
isCover: boolean
}
// ~~~~1.文档片段展示功能~~~~
const visible = ref(false) // v-model:显式隐藏modal
const title = ref("测评大纲") // modal的标题
const projectId = ref<number | null>(null) // 传入的项目id
const fragmentListPending = ref(false) // 片段列表请求的loading状态
// 定义文档片段储存
const fragmentList = ref<IFragmentItem[]>([])
// out暴露出去的函数
const open = async (documentType: DocumentType, id: number) => {
projectId.value = id // 传入的项目id
title.value = documentType
visible.value = true
// 打开时清空fragmentList数据
fragmentList.value = []
// 打开时请求数据
fragmentListPending.value = true // 设置loading状态
// 请求片段列表数据
try {
const { data } = await fragmentApi.getFragmentByDocumentType({
id: projectId.value,
documentType
})
// 填充到fragmentList
fragmentList.value = data.map((it: string) => ({
name: it,
isCover: false
}))
fragmentListPending.value = false
} catch (err) {
fragmentListPending.value = false // 请求失败关闭loading状态
}
}
// 全勾选/全不勾选按钮
const allCheckBtn = () => {
fragmentList.value.forEach((item) => {
item.isCover = true // 全部勾选
})
}
const allUnCheckBtn = () => {
fragmentList.value.forEach((item) => {
item.isCover = false // 全部不勾选
})
}
// ~~~~2.文件上传功能~~~~
// 上传失败的事件 - 报错内容由后端定义message字段
const uploadErrorHandle = (fielItem: any) => {
const res = JSON.parse(fielItem.response)
if (res.message) {
Message.error(res.message)
}
}
// 上传弹窗事件
const confirmUploadHandle = (file: any) => {
return new Promise((resolve, reject) => {
Modal.confirm({
title: "请确认您上传的文件是带有文档片段的docx文档",
content: `${file.name}`,
onOk: () => resolve(true),
onCancel: () => reject("cancel")
})
})
}
// ~~~~3.产品文档下载功能~~~~
// 注意二段文档生成成功后,需要刷新片段列表数据(待完成)
const downloadHandle = async () => {
// 判断产品文档类型
const documentType = title.value
try {
// 二段文档异步请求
switch (documentType) {
case "测评大纲":
await createDgItem(projectId.value!)
break
case "测试说明":
await createSmItem(projectId.value!)
break
case "测试记录":
await createJLItem(projectId.value!)
break
case "回归测试说明":
await createHsmItem(projectId.value!)
break
case "回归测试记录":
await createHjlItem(projectId.value!)
break
case "问题单":
await createWtdItem(projectId.value!)
break
case "测评报告":
await createBgItem(projectId.value!)
break
default:
break
}
// 生成最终产品文档请求 -> 添加当前用户取消选择的片段
switch (documentType) {
case "测评大纲":
await createSeitaiDagang(projectId.value!, documentType, fragmentList.value)
break
case "测试说明":
await createSeitaiShuoming(projectId.value!, documentType, fragmentList.value)
break
case "测试记录":
await createSeitaiJilu(projectId.value!, documentType, fragmentList.value)
break
case "回归测试说明":
await createSeitaiHsm(projectId.value!, documentType, fragmentList.value)
break
case "回归测试记录":
await createSeitaiHjl(projectId.value!, documentType, fragmentList.value)
break
case "问题单":
await createSeitaiWtd(projectId.value!, documentType, fragmentList.value)
break
case "测评报告":
await createSeitaiBaogao(projectId.value!, documentType, fragmentList.value)
break
default:
break
}
// 为了方便直接关闭弹窗这样用户可以使用open函数初始化
visible.value = false
} catch (err) {
// 总体出错处理,关闭弹窗
visible.value = false
}
}
// hook-二段文档函数
const {
isGenerating,
createDgItem,
createSmItem,
createJLItem,
createHsmItem,
createHjlItem,
createWtdItem,
createBgItem
} = useGenerateSecond()
// hook-生成产品文档
const {
visible: progressVisible,
isComplete,
handleModalConfirmClick,
createSeitaiDagang,
createSeitaiShuoming,
createSeitaiJilu,
createSeitaiHsm,
createSeitaiHjl,
createSeitaiWtd,
createSeitaiBaogao
} = useSeitaiModal()
defineExpose({ open })
defineOptions({
name: "GeneratorModal"
})
</script>
<style lang="less" scoped>
.fragment-item {
display: flex;
align-items: center;
justify-content: space-around;
.fragment-name {
width: 40%;
}
}
.button-list {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-left: 20px;
}
.alist {
margin: 10px 0;
}
.buttonAndAlert {
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@@ -4,23 +4,31 @@ export default {
/**
* 生成最终产品文档的进度条模块
*/
async create_entire_doc(visible, isComplete, api, record_id, docName) {
async create_entire_doc(visible, isComplete, api, record_id, docName, fragmentList) {
visible.value = true
isComplete.value = false
const st = await api({ id: record_id }).catch((err) => {
try {
const st = await api({ id: record_id, frag: fragmentList })
// 动态生成文件名
const fileType = st.type
const fileExt = fileType.includes("zip") ? "zip" : "docx"
const fileName = `${docName}.${fileExt}`
// 下面是创建后Blob并触发下载
const blob = new Blob([st], {
type: fileType
})
const url = window.URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = fileName // 设置文件名(动态)
a.click()
window.URL.revokeObjectURL(url) // 释放 URL 对象
// 上面是触发下载
isComplete.value = true
Message.success("文档生成并下载成功!")
} catch (err) {
isComplete.value = true
visible.value = false
})
// 下面是创建后Blob并触发下载
const blob = new Blob([st], { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" })
const url = window.URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = `${docName}.docx` // 设置下载文件名
a.click()
window.URL.revokeObjectURL(url) // 释放 URL 对象
// 上面是触发下载
isComplete.value = true
Message.success("文档生成并下载成功!")
}
}
}

View File

@@ -10,14 +10,14 @@ const useCrudInit = function () {
rowSelection: { showCheckedAll: true },
api: projectApi.getPageList,
add: { show: true, api: projectApi.save, text: "新增项目" },
edit: { show: true, api: projectApi.update, text: "编辑项目" }, // auth未空数组则所有都可以
edit: { show: true, api: projectApi.update, text: "编辑" }, // auth未空数组则所有都可以
delete: { show: true, api: projectApi.delete },
searchColNumber: 3,
tablePagination: false,
operationColumn: true,
operationWidth: 500,
showIndex: false,
operationColumnWidth: 280, // 操作列宽度
operationColumnWidth: 400, // 操作列宽度
operationColumnAlign: "center", // 操作列对齐方式
afterDelete(response: any) {
crudRef.value.tableRef.selectAll(false)
@@ -37,17 +37,8 @@ const useCrudInit = function () {
{
formType: "grid",
cols: [
{ span: 8, formList: [{ dataIndex: "ident" }] },
{ span: 8, formList: [{ dataIndex: "name" }] },
{ span: 8, formList: [{ dataIndex: "engin_model" }] }
]
},
{
formType: "grid",
cols: [
{ span: 8, formList: [{ dataIndex: "section_system" }] },
{ span: 8, formList: [{ dataIndex: "sub_system" }] },
{ span: 8, formList: [{ dataIndex: "device" }] }
{ span: 4, formList: [{ dataIndex: "ident" }] },
{ span: 8, formList: [{ dataIndex: "name" }] }
]
},
{
@@ -150,7 +141,7 @@ const useCrudInit = function () {
{
title: "项目标识",
align: "center",
width: 90,
width: 80,
sortable: { sortDirections: ["ascend"] },
dataIndex: "ident",
search: true,
@@ -163,28 +154,26 @@ const useCrudInit = function () {
},
{
title: "项目名称",
width: 110,
width: 120,
align: "center",
dataIndex: "name",
search: true,
commonRules: [{ required: true, message: "名称是必填" }]
},
{ title: "工程型号", dataIndex: "engin_model", hide: true },
{ title: "分系统", dataIndex: "section_system", hide: true },
{ title: "子系统", dataIndex: "sub_system", hide: true },
{ title: "设备名称", dataIndex: "device", hide: true },
{
title: "开始日期",
dataIndex: "beginTime",
align: "center",
commonRules: [{ required: true, message: "开始时间必填" }],
formType: "date"
formType: "date",
width: 110
},
{
title: "结束时间",
align: "center",
dataIndex: "endTime",
formType: "date",
width: 110,
extra: "注意:结束时间需要晚于最后一轮结束时间",
commonRules: [
{
@@ -212,7 +201,7 @@ const useCrudInit = function () {
{
title: "责任人",
align: "center",
width: 70,
width: 50,
dataIndex: "duty_person",
search: true,
commonRules: [{ required: true, message: "责任人必选" }],
@@ -352,6 +341,7 @@ const useCrudInit = function () {
align: "center",
addDefaultValue: "9",
search: true,
width: 70,
commonRules: [{ required: true, message: "报告类型必填" }],
// 字典-report_type
formType: "radio",
@@ -371,7 +361,7 @@ const useCrudInit = function () {
{
title: "依据标准",
dataIndex: "standard",
addDefaultValue: ["1", "2", "3", "4", "9"],
addDefaultValue: ["16", "2", "17", "3", "7", "4", "5", "6"],
maxTagCount: 20,
commonRules: [{ required: true, message: "请至少选择一个" }],
hide: true,
@@ -510,6 +500,7 @@ const useCrudInit = function () {
{
title: "状态",
align: "center",
width: 80,
dataIndex: "step",
search: true,
formType: "radio",

View File

@@ -1,3 +1,6 @@
/**
* 该文件是配合请求后端生成各文档的二段文档
*/
import { ref } from "vue"
import dgGenerateApi from "@/api/generate/dgGenerate"
import smGenerateApi from "@/api/generate/smGenerate"
@@ -6,7 +9,6 @@ import bgGenerateApi from "@/api/generate/bgGenerate"
import hsmGenerateApi from "@/api/generate/hsmGenerate"
import hjlGenerateApi from "@/api/generate/hjlGenerate"
import wtdGenerateApi from "@/api/generate/wtdGenerate"
import { Message } from "@arco-design/web-vue"
const useGenerateSecond = function () {
// refs
@@ -20,53 +22,10 @@ const useGenerateSecond = function () {
const ishjlLoading = ref(false)
const isWtdLoading = ref(false)
// events
// 记录生成二级文档
const createJLItem = async (record: any) => {
isGenerating.value = true
isJlloading.value = true
await jlGenerateApi.createJLcaserecord({ id: record.id }).finally(() => {
isGenerating.value = false
isJlloading.value = false
})
Message.success("记录-片段库生成成功请查看output/jl文件夹")
}
// 说明生成二级文档
const createSmItem = async (record: any) => {
isGenerating.value = true
isSmLoading.value = true
const id = record.id
await Promise.all([
dgGenerateApi.createSoftComposition({ id }), // 生成测评对象 - 和大纲一样
dgGenerateApi.createFuncList({ id }), // 生成被测软件功能 - 和大纲重复
dgGenerateApi.createInterface({ id }), // 生成被测软件接口 - 和大纲重复 - 可能会删除
dgGenerateApi.createPerformance({ id }), // 生成被测软件性能 - 和大纲重复 - 可能会删除
dgGenerateApi.createBaseInformation({ id }), // 生成被测软件基本信息 - 和大纲重复 - 可能会删除
dgGenerateApi.createYiju({ id }), // 生成标准类引用文档 - 和大纲重复 - 可能会删除
smGenerateApi.createSMTechyiju({ id }), // 生成技术类引用文档列表 -> 在大纲基础上添加《测评大纲》
// 拆分软硬件环境
dgGenerateApi.createStaticEnvironment({ id }), // 生成-静态测试环境说明
dgGenerateApi.createStaticSoft({ id }), // 生成-静态软件项
dgGenerateApi.createStaticHard({ id }), // 生成-静态硬件和固件项
dgGenerateApi.createDynamicEnv({ id }), // 生成-动态测试环境说明
dgGenerateApi.createDynamicSoft({ id }), // 生成-动态软件项
dgGenerateApi.createDynamicHard({ id }), // 生成-动态硬件和固件项
dgGenerateApi.createTestData({ id }), // 生成-测评数据
dgGenerateApi.createEnvDiff({ id }), // 生成-环境差异性分析
// ~~~
smGenerateApi.createSMCaseList({ id }), // 生成用例全
smGenerateApi.createSMCaseBreifList({ id }), // 生成用例列表-那个表格
smGenerateApi.createSMTrack({ id }) // 生成说明追踪
]).finally(() => {
isGenerating.value = false
isSmLoading.value = false
})
Message.success("说明-片段库生成成功请查看output/sm文件夹")
}
// 大纲生成二级文档
const createDgItem = async (e: any, record: any) => {
const createDgItem = async (id: number) => {
isGenerating.value = true
isDgLoading.value = true
const id = record.id
await Promise.all([
dgGenerateApi.createTestDemand({ id }), // 生成第一轮测试项
dgGenerateApi.createYiju({ id }), // 生成依据文件
@@ -97,19 +56,104 @@ const useGenerateSecond = function () {
dgGenerateApi.createDynamicHard({ id }), // 生成-动态硬件和固件项
dgGenerateApi.createTestData({ id }), // 生成-测评数据
dgGenerateApi.createEnvDiff({ id }), // 生成-环境差异性分析
// ~~~~~~~~~
dgGenerateApi.createMainTech({ id }) // 生成-主要战技指标
]).finally(() => {
isGenerating.value = false
isDgLoading.value = false
})
Message.success("大纲-片段库文档生成成功请查看output/dg文件夹")
}
// 说明生成二级文档
const createSmItem = async (id: number) => {
isGenerating.value = true
isSmLoading.value = true
await Promise.all([
dgGenerateApi.createSoftComposition({ id }), // 生成测评对象 - 和大纲一样
dgGenerateApi.createFuncList({ id }), // 生成被测软件功能 - 和大纲重复
dgGenerateApi.createInterface({ id }), // 生成被测软件接口 - 和大纲重复 - 可能会删除
dgGenerateApi.createPerformance({ id }), // 生成被测软件性能 - 和大纲重复 - 可能会删除
dgGenerateApi.createBaseInformation({ id }), // 生成被测软件基本信息 - 和大纲重复 - 可能会删除
dgGenerateApi.createYiju({ id }), // 生成标准类引用文档 - 和大纲重复 - 可能会删除
smGenerateApi.createSMTechyiju({ id }), // 生成技术类引用文档列表 -> 在大纲基础上添加《测评大纲》
// 拆分软硬件环境
dgGenerateApi.createStaticEnvironment({ id }), // 生成-静态测试环境说明
dgGenerateApi.createStaticSoft({ id }), // 生成-静态软件项
dgGenerateApi.createStaticHard({ id }), // 生成-静态硬件和固件项
dgGenerateApi.createDynamicEnv({ id }), // 生成-动态测试环境说明
dgGenerateApi.createDynamicSoft({ id }), // 生成-动态软件项
dgGenerateApi.createDynamicHard({ id }), // 生成-动态硬件和固件项
dgGenerateApi.createTestData({ id }), // 生成-测评数据
dgGenerateApi.createEnvDiff({ id }), // 生成-环境差异性分析
// ~~~
smGenerateApi.createSMCaseList({ id }), // 生成用例全
smGenerateApi.createSMCaseBreifList({ id }), // 生成用例列表-那个表格
smGenerateApi.createSMTrack({ id }) // 生成说明追踪
]).finally(() => {
isGenerating.value = false
isSmLoading.value = false
})
}
// 记录生成二级文档
const createJLItem = async (id: number) => {
isGenerating.value = true
isJlloading.value = true
await jlGenerateApi.createJLcaserecord({ id }).finally(() => {
isGenerating.value = false
isJlloading.value = false
})
}
// 回归测试说明二级文档
const createHsmItem = async (id: number) => {
isGenerating.value = true
ishsmLoading.value = true
await Promise.all([
hsmGenerateApi.deleteHSMFiles({ id }), // 先删除以前文件
hsmGenerateApi.createBasicInfo({ id }),
hsmGenerateApi.createDocSummary({ id }),
hsmGenerateApi.createJstech({ id }),
hsmGenerateApi.createChangePart({ id }),
hsmGenerateApi.createHdemand({ id }),
hsmGenerateApi.createCaseListDesc({ id }),
hsmGenerateApi.createCaseList({ id }),
hsmGenerateApi.createTrack({ id }),
// 拆分大纲软硬件环境
dgGenerateApi.createStaticEnvironment({ id }), // 生成-静态测试环境说明
dgGenerateApi.createStaticSoft({ id }), // 生成-静态软件项
dgGenerateApi.createStaticHard({ id }), // 生成-静态硬件和固件项
dgGenerateApi.createDynamicEnv({ id }), // 生成-动态测试环境说明
dgGenerateApi.createDynamicSoft({ id }), // 生成-动态软件项
dgGenerateApi.createDynamicHard({ id }), // 生成-动态硬件和固件项
dgGenerateApi.createTestData({ id }), // 生成-测评数据
dgGenerateApi.createEnvDiff({ id }) // 生成-环境差异性分析
]).finally(() => {
isGenerating.value = false
ishsmLoading.value = false
})
}
// 回归测试记录二级文档
const createHjlItem = async (id: number) => {
isGenerating.value = true
ishjlLoading.value = true
await Promise.all([
hjlGenerateApi.deleteHJLFiles({ id }), // 先调用删除文件夹里面文件
hjlGenerateApi.createBasicInfo({ id }),
hjlGenerateApi.createCaseinfo({ id })
]).finally(() => {
isGenerating.value = false
ishjlLoading.value = false
})
}
// 问题单二级文档
const createWtdItem = async (id: number) => {
isGenerating.value = true
isWtdLoading.value = true
await wtdGenerateApi.createWtdTable({ id }).finally(() => {
isGenerating.value = false
isWtdLoading.value = false
})
}
// 报告生成二级文档
const createBgItem = async (record: any) => {
const createBgItem = async (id: number) => {
isGenerating.value = true
isBgLoading.value = true
const id = record.id
await Promise.all([
bgGenerateApi.deleteBGFiles({ id }), // 删除output/bg文件夹下文件
bgGenerateApi.createBgTechYiju({ id }),
@@ -138,64 +182,7 @@ const useGenerateSecond = function () {
isGenerating.value = false
isBgLoading.value = false
})
Message.success("报告-片段库文档生成成功请查看output/bg文件夹")
}
// 回归测试说明二级文档
const createHsmItem = async (record: any) => {
const id = record.id
isGenerating.value = true
ishsmLoading.value = true
await Promise.all([
hsmGenerateApi.deleteHSMFiles({ id }), // 先删除以前文件
hsmGenerateApi.createBasicInfo({ id }),
hsmGenerateApi.createDocSummary({ id }),
hsmGenerateApi.createJstech({ id }),
hsmGenerateApi.createChangePart({ id }),
hsmGenerateApi.createHdemand({ id }),
hsmGenerateApi.createCaseListDesc({ id }),
hsmGenerateApi.createCaseList({ id }),
hsmGenerateApi.createTrack({ id }),
// 拆分大纲软硬件环境
dgGenerateApi.createStaticEnvironment({ id }), // 生成-静态测试环境说明
dgGenerateApi.createStaticSoft({ id }), // 生成-静态软件项
dgGenerateApi.createStaticHard({ id }), // 生成-静态硬件和固件项
dgGenerateApi.createDynamicEnv({ id }), // 生成-动态测试环境说明
dgGenerateApi.createDynamicSoft({ id }), // 生成-动态软件项
dgGenerateApi.createDynamicHard({ id }), // 生成-动态硬件和固件项
dgGenerateApi.createTestData({ id }), // 生成-测评数据
dgGenerateApi.createEnvDiff({ id }) // 生成-环境差异性分析
]).finally(() => {
isGenerating.value = false
ishsmLoading.value = false
})
Message.success("回归说明-片段库文档生成成功请查看output/hsm文件夹")
}
// 回归测试记录二级文档
const createHjlItem = async (record: any) => {
const id = record.id
isGenerating.value = true
ishjlLoading.value = true
await Promise.all([
hjlGenerateApi.deleteHJLFiles({ id }), // 先调用删除文件夹里面文件
hjlGenerateApi.createBasicInfo({ id }),
hjlGenerateApi.createCaseinfo({ id })
]).finally(() => {
isGenerating.value = false
ishjlLoading.value = false
})
Message.success("回归记录-片段库文档生成成功请查看output/hjl文件夹")
}
// 问题单二级文档
const createWtdItem = async (record: any) => {
isGenerating.value = true
isWtdLoading.value = true
await wtdGenerateApi.createWtdTable({ id: record.id }).finally(() => {
isGenerating.value = false
isWtdLoading.value = false
})
Message.success("问题单-片段库文档生成成功请查看output/wtd文件夹")
}
return {
isGenerating,
isDgLoading,

View File

@@ -1,69 +1,61 @@
/**
* 生成产品文档按钮
*/
import { ref } from "vue"
import hoosk from "./hooks"
import seitaiGenerateApi from "@/api/generate/seitaiGenerate"
import type { IFragmentItem } from "@/views/testmanage/projmanage/GeneratorModal/index.vue"
const useSeitaiModal = function () {
// refs
const visible = ref(false)
const isComplete = ref(false)
const ptext = ref("测评大纲")
const visible = ref(false) // Modal显隐
const isComplete = ref(false) // 是否生成完成
// events
const handleModalConfirmClick = () => {
visible.value = false
visible.value = false // 关闭Modal
}
// 生成文档
// ~~~~~~~~测试说明生成文档~~~~~~~~
const createSeitaiShuoming = async (record: any) => {
ptext.value = "测试说明"
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createShuomingSeiTai, record.id, ptext.value)
}
// ~~~~~~~~测试大纲生成文档~~~~~~~~
const createSeitaiDagang = async (record: any) => {
const createSeitaiDagang = async (id: number, ptext: string, fragmentList: IFragmentItem[]) => {
// 根据一系列文档生成大纲 - 这里有进度条组件、a-modal组件
// 1.打开进度条组件
ptext.value = "测评大纲"
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createDagangSeiTai, record.id, ptext.value)
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createDagangSeiTai, id, ptext, fragmentList)
}
// ~~~~~~~~测试说明生成文档~~~~~~~~
const createSeitaiShuoming = async (id: number, ptext: string, fragmentList: IFragmentItem[]) => {
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createShuomingSeiTai, id, ptext, fragmentList)
}
// ~~~~~~~~记录生成文档~~~~~~~~
const createSeitaiJilu = async (record: any) => {
ptext.value = "测试记录"
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createJiluSeiTai, record.id, ptext.value)
}
// ~~~~~~~~报告生成文档~~~~~~~~
const createSeitaiBaogao = async (record: any) => {
ptext.value = "测评报告"
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createBgDocument, record.id, ptext.value)
const createSeitaiJilu = async (id: number, ptext: string, fragmentList: IFragmentItem[]) => {
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createJiluSeiTai, id, ptext, fragmentList)
}
// ~~~~~~~~回归测试说明~~~~~~~~
const createSeitaiHsm = async (record: any) => {
ptext.value = "回归测试说明"
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createHsmDocument, record.id, ptext.value)
const createSeitaiHsm = async (id: number, ptext: string, fragmentList: IFragmentItem[]) => {
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createHsmDocument, id, ptext, fragmentList)
}
// ~~~~~~~~回归测试记录~~~~~~~~
const createSeitaiHjl = async (record: any) => {
ptext.value = "回归测试记录"
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createHjlDocument, record.id, ptext.value)
const createSeitaiHjl = async (id: number, ptext: string, fragmentList: IFragmentItem[]) => {
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createHjlDocument, id, ptext, fragmentList)
}
// ~~~~~~~~问题单~~~~~~~~
const createSeitaiWtd = async (record: any) => {
ptext.value = "问题单"
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createWtdDocument, record.id, ptext.value)
const createSeitaiWtd = async (id: number, ptext: string, fragmentList: IFragmentItem[]) => {
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createWtdDocument, id, ptext, fragmentList)
}
// ~~~~~~~~报告生成文档~~~~~~~~
const createSeitaiBaogao = async (id: number, ptext: string, fragmentList: IFragmentItem[]) => {
hoosk.create_entire_doc(visible, isComplete, seitaiGenerateApi.createBgDocument, id, ptext, fragmentList)
}
return {
visible,
isComplete,
ptext,
handleModalConfirmClick,
createSeitaiShuoming,
createSeitaiDagang,
createSeitaiShuoming,
createSeitaiJilu,
createSeitaiBaogao,
createSeitaiHsm,
createSeitaiHjl,
createSeitaiWtd
createSeitaiWtd,
createSeitaiBaogao
}
}

View File

@@ -5,89 +5,25 @@
<!-- ma-crud组件 -->
<ma-crud :options="crudOptions" :columns="crudColumns" ref="crudRef">
<template #operationBeforeExtend="{ record }">
<a-popover title="文档生成组合按钮" trigger="click">
<a-popover title="点击配置生成文档" trigger="click">
<a-button type="primary" size="mini">
<template #icon>
<icon-plus />
<icon-download />
</template>
文档生成
</a-button>
<template #content>
<p>
<a-link
:disabled="isGenerating"
:loading="isDgLoading"
@click="createDgItem($event, record)"
>
大纲二段文档
</a-link>
</p>
<p>
<a-link :disabled="isGenerating" :loading="isSmLoading" @click="createSmItem(record)">
说明二段文档
</a-link>
</p>
<p>
<a-link :disabled="isGenerating" :loading="isJlloading" @click="createJLItem(record)">
记录二段文档
</a-link>
</p>
<p>
<a-link :disabled="isGenerating" :loading="isBgLoading" @click="createBgItem(record)">
报告二段文档
</a-link>
</p>
<p>
<a-link :disabled="isGenerating" :loading="ishsmLoading" @click="createHsmItem(record)">
回归说明二段文档
</a-link>
</p>
<p>
<a-link :disabled="isGenerating" :loading="ishjlLoading" @click="createHjlItem(record)">
回归记录二段文档
</a-link>
</p>
<p>
<a-link :disabled="isGenerating" :loading="isWtdLoading" @click="createWtdItem(record)">
问题单二段文档
</a-link>
</p>
<p>
<a-link :disabled="isGenerating" @click="createSeitaiDagang(record)">
<icon-eye />
[测试]生成最后大纲
</a-link>
</p>
<p>
<a-link :disabled="isGenerating" @click="createSeitaiShuoming(record)">
<icon-eye />[测试]生成最后说明
</a-link>
</p>
<p>
<a-link :disabled="isGenerating" @click="createSeitaiJilu(record)"
><icon-eye />[测试]生成最后记录</a-link
>
</p>
<p>
<a-link :disabled="isGenerating" @click="createSeitaiBaogao(record)"
><icon-eye />[测试]生成测评报告</a-link
>
</p>
<p>
<a-link :disabled="isGenerating" @click="createSeitaiHsm(record)"
><icon-eye />[测试]回归测试说明</a-link
>
</p>
<p>
<a-link :disabled="isGenerating" @click="createSeitaiHjl(record)"
><icon-eye />[测试]回归测试记录</a-link
>
</p>
<p>
<a-link :disabled="isGenerating" @click="createSeitaiWtd(record)"
><icon-eye />[测试]生成问题单</a-link
>
</p>
<a-space direction="vertical" :size="0" align="start">
<a-space direction="vertical" :size="0" align="start">
<a-link @click="openCreateModal('测评大纲', record.id)">测评大纲</a-link>
<a-link @click="openCreateModal('测试说明', record.id)">测试说明</a-link>
<a-link @click="openCreateModal('测试记录', record.id)">测试记录</a-link>
<a-link @click="openCreateModal('回归测试说明', record.id)">回归测试说明</a-link>
<a-link @click="openCreateModal('回归测试记录', record.id)">回归测试记录</a-link>
<a-link @click="openCreateModal('问题单', record.id)">问题单</a-link>
<a-link @click="openCreateModal('测评报告', record.id)">测评报告</a-link>
</a-space>
</a-space>
</template>
</a-popover>
<a-button @click="enterWorkPlant(record)" size="mini" status="warning" type="outline">
@@ -95,70 +31,33 @@
</a-button>
<a-link @click="previewRef.open(record)"><icon-eye />预览</a-link>
<a-link @click="handleFragmentClick(record)"><icon-file />片段</a-link>
<a-link @click="handleBoardClick(record)"><icon-dashboard />项目看板</a-link>
<a-link @click="handleBoardClick(record)"><icon-dashboard />看板</a-link>
</template>
</ma-crud>
<GeneratorModal ref="generatorModalRef" />
</div>
<preview ref="previewRef" :columns="crudColumns"></preview>
<Progress
:visible="visible"
:isComplete="isComplete"
:text="ptext"
@clickConfirm="handleModalConfirmClick"
></Progress>
</div>
</template>
<script setup lang="jsx">
<script setup lang="ts">
import { ref } from "vue"
import { useRouter } from "vue-router"
import preview from "./cpns/preview.vue"
import Progress from "./cpns/progress.vue"
import useEnterWorkPlant from "./hooks/useEnterWorkPlant"
import useSeitaiModal from "./hooks/useSeitaiModal"
import useGenerateSecond from "./hooks/useGenerateSecond"
import preview from "./cpns/preview.vue" // 项目详情预览组件
import useEnterWorkPlant from "./hooks/useEnterWorkPlant" // 进入工作区逻辑
import useCrudInit from "./hooks/useCrudInit"
import GeneratorModal from "./GeneratorModal/index.vue" // 生成文档Modal组件
import type { DocumentType } from "@/utils/types/CommonType" // 产品文档类型
const router = useRouter()
// crud配置和字段信息定义
const { crudRef, crudOptions, crudColumns } = useCrudInit()
// 点击进入工作区函数 - 每次点击后都清除localStorage中树状目录数据
const { enterWorkPlant } = useEnterWorkPlant()
// 生成最终文档事件,并且弹窗显隐和退出条件判断
const {
visible,
isComplete,
ptext,
handleModalConfirmClick,
createSeitaiShuoming,
createSeitaiDagang,
createSeitaiJilu,
createSeitaiBaogao,
createSeitaiHsm,
createSeitaiHjl,
createSeitaiWtd
} = useSeitaiModal()
// 用于生成二段文档按钮事件和禁用按钮ref
const {
isGenerating,
isDgLoading,
isSmLoading,
isBgLoading,
isJlloading,
ishsmLoading,
ishjlLoading,
isWtdLoading,
createJLItem,
createSmItem,
createDgItem,
createBgItem,
createHsmItem,
createHjlItem,
createWtdItem
} = useGenerateSecond()
// 其他功能
// 1.跳转到项目看板页面
const handleBoardClick = (record) => {
const handleBoardClick = (record: any) => {
router.push({
name: "projBoard",
params: {
@@ -167,7 +66,7 @@ const handleBoardClick = (record) => {
})
}
// 2.跳转到项目所属文档片段
const handleFragmentClick = (record) => {
const handleFragmentClick = (record: any) => {
router.push({
name: "projFragment",
params: {
@@ -178,14 +77,19 @@ const handleFragmentClick = (record) => {
// 3.预览项目信息ref
const previewRef = ref()
// 4.生成文档组件
const generatorModalRef = ref<InstanceType<typeof GeneratorModal> | null>(null) // GeneratorModal组件的ref
const openCreateModal = (documentType: DocumentType, id: number) => {
generatorModalRef.value!.open(documentType, id) // 打开弹出框
}
defineOptions({
name: "projmanage"
})
</script>
<style lang="less" scoped>
<style scoped lang="less">
.msg-menu {
// border-right: 1px solid var(--color-border-2);
& li {
border-radius: 1px;
cursor: pointer;