文档片段全面改进,不使用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

581
cdTMP/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "cdtmp",
"private": true,
"version": "0.0.4",
"version": "0.0.5",
"type": "module",
"scripts": {
"dev": "vite",
@@ -13,9 +13,9 @@
"dependencies": {
"@arco-design/color": "^0.4.0",
"@arco-design/web-vue": "^2.57.0",
"@tanstack/vue-query": "^5.71.1",
"@tanstack/vue-query": "^5.74.5",
"@tinymce/tinymce-vue": "^6.1.0",
"@vueuse/core": "^13.0.0",
"@vueuse/core": "^13.1.0",
"axios": "^1.8.4",
"dayjs": "^1.11.13",
"file2md5": "^1.3.0",
@@ -23,39 +23,39 @@
"mammoth": "^1.9.0",
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"pinia": "^3.0.1",
"pinia": "^3.0.2",
"pinyin-match": "^1.2.6",
"postcss-import": "^16.1.0",
"qs": "^6.14.0",
"tinymce": "^7.7.2",
"tinymce": "^7.8.0",
"vue": "^3.5.13",
"vue-clipboard3": "^2.0.0",
"vue-color-kit": "^1.0.6",
"vue-data-ui": "^2.6.27",
"vue-data-ui": "^2.6.40",
"vue-router": "^4.5.0",
"vuedraggable": "^2.24.3"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.0",
"@tailwindcss/vite": "^4.1.0",
"@tailwindcss/postcss": "^4.1.4",
"@tailwindcss/vite": "^4.1.4",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.13.17",
"@types/node": "^22.14.1",
"@types/nprogress": "^0.2.3",
"@types/qs": "^6.9.18",
"@vitejs/plugin-vue": "^5.2.3",
"@vitejs/plugin-vue-jsx": "^4.1.2",
"@vue/babel-plugin-jsx": "^1.4.0",
"browserslist": "^4.24.4",
"eslint": "^9.23.0",
"eslint": "^9.25.0",
"eslint-plugin-vue": "^10.0.0",
"less": "^4.2.2",
"less": "^4.3.0",
"less-loader": "^12.2.0",
"postcss": "^8.5.3",
"prettier": "^3.5.3",
"rollup-plugin-visualizer": "^5.14.0",
"tailwindcss": "^4.1.0",
"typescript": "^5.8.2",
"vite": "^6.2.4",
"vue-eslint-parser": "^10.1.1"
"tailwindcss": "^4.1.4",
"typescript": "^5.8.3",
"vite": "^6.3.2",
"vue-eslint-parser": "^10.1.3"
}
}

View File

@@ -308,15 +308,4 @@ export default {
params
})
},
/**
*
* @returns 生成-主要战技指标
*/
createMainTech(params = {}) {
return request({
url: `/generate/create/mainTech`,
method: "get",
params
})
}
}

View File

@@ -0,0 +1,15 @@
import { request } from "@/api/request"
export default {
/**
* 根据项目id和文档类型获取文档片段名称
* @returns 文档片段名称数组
*/
getFragmentByDocumentType(params: { id: number; documentType: string }) {
return request({
url: `/createfragment/get_fragments`,
method: "get",
params
})
}
}

View File

@@ -0,0 +1,13 @@
import { request } from "@/api/request"
export default {
/**
* 获取当前项目非第一轮有几轮测试
*/
getHgRoundNumber(params: { id: number }) {
return request({
url: `/createfragment/get_round_exit`,
method: "get",
params
})
}
}

View File

@@ -4,11 +4,11 @@ export default {
* 如果缺少部分文件给与提示
* @returns 根据output_dir生成最终大纲文档
*/
createDagangSeiTai(params = {}) {
createDagangSeiTai(data = {}) {
return request({
url: `/create/dgDocument`,
method: "get",
params,
method: "post",
data,
responseType: "blob"
})
},
@@ -16,11 +16,11 @@ export default {
* 如果缺少部分文件给与提示
* @returns 根据output_dir以及output_dir/sm中文档生成测试说明
*/
createShuomingSeiTai(params = {}) {
createShuomingSeiTai(data = {}) {
return request({
url: `/create/smDocument`,
method: "get",
params,
method: "post",
data,
responseType: "blob"
})
},
@@ -28,23 +28,11 @@ export default {
* TODO:如果缺少部分文件给与提示
* @returns 根据output_dir以及output_dir/JL中文档生成测试记录
*/
createJiluSeiTai(params = {}) {
createJiluSeiTai(data = {}) {
return request({
url: `/create/jlDocument`,
method: "get",
params,
responseType: "blob"
})
},
/**
* TODO:如果缺少部分文件给与提示
* @returns 根据output_dir以及output_dir/bg中文档生成测评报告
*/
createBgDocument(params = {}) {
return request({
url: `/create/bgDocument`,
method: "get",
params,
method: "post",
data,
responseType: "blob"
})
},
@@ -52,11 +40,11 @@ export default {
* TODO:如果缺少部分文件给与提示
* @returns 根据output_dir以及output_dir/hsm中文档生成回归测试说明特殊多个文件
*/
createHsmDocument(params = {}) {
createHsmDocument(data = {}) {
return request({
url: `/create/hsmDocument`,
method: "get",
params,
method: "post",
data,
responseType: "blob"
})
},
@@ -64,11 +52,11 @@ export default {
* TODO:如果缺少部分文件给与提示
* @returns 根据output_dir以及output_dir/hjl中文档生成回归测试记录特殊多个文件
*/
createHjlDocument(params = {}) {
createHjlDocument(data = {}) {
return request({
url: `/create/hjlDocument`,
method: "get",
params,
method: "post",
data,
responseType: "blob"
})
},
@@ -76,11 +64,23 @@ export default {
* TODO:生成最终问题单
* @returns 返回是否正确生成问题单
*/
createWtdDocument(params = {}) {
createWtdDocument(data = {}) {
return request({
url: `/create/wtdDocument`,
method: "get",
params,
method: "post",
data,
responseType: "blob"
})
},
/**
* TODO:如果缺少部分文件给与提示
* @returns 根据output_dir以及output_dir/bg中文档生成测评报告
*/
createBgDocument(data = {}) {
return request({
url: `/create/bgDocument`,
method: "post",
data,
responseType: "blob"
})
},
@@ -92,7 +92,7 @@ export default {
return request({
url: `/create/cancel`,
method: "get",
params,
params
})
}
}

View File

@@ -41,12 +41,28 @@ function createService() {
return res.data
},
(error) => {
const err = (text) => {
Message.error({
content:
const err = async (text) => {
let content = ""
// 在设置axios为responseType: "blob"时候data为Blob对象需要解析Blob
if (error.response && error.response.data instanceof Blob) {
try {
const text = await error.response.data.text()
content = JSON.parse(text).message && JSON.parse(text).message
if (!content) {
content = "未知Blob错误"
}
} catch (e) {
Message.error("解析Blob失败")
}
} else {
// 非Blob正常错误响应
content =
error.response && error.response.data && error.response.data.message
? error.response.data.message
: text,
: text
}
Message.error({
content,
icon: () => h(IconFaceFrownFill)
})
}
@@ -58,6 +74,9 @@ function createService() {
case 500:
err("服务器内部错误")
break
case 400:
err("服务器抛出逻辑错误")
break
case 401:
err("登录状态已过期,需要重新登录")
// 清除本地localStorage

7
cdTMP/src/arco-design-types.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
import type { ArcoGlobalComponents } from "@arco-design/web-vue"
declare module "vue" {
export interface GlobalComponents extends ArcoGlobalComponents {
ASpace: (typeof import("@arco-design/web-vue"))["Space"]
}
}

View File

@@ -5,9 +5,9 @@
</div>
</template>
<script setup>
import { ref } from "vue"
const text = ref("暂无数据")
<script setup lang="ts">
// 标准vue3.5+的默认值写法、结构写法、ts类型写法
const { text = "暂无数据" } = defineProps<{ text?: string }>()
</script>
<style lang="less" scoped>

View File

@@ -379,9 +379,4 @@ const refresh = () => {
defineExpose({ deleteAction, recoveryAction })
</script>
<style scoped>
:deep(.arco-image-img) {
object-fit: contain;
background-color: var(--color-fill-4);
}
</style>
<style scoped></style>

View File

@@ -1,7 +1,3 @@
<!--
- @Author XXX
- @Link XXX
-->
<template>
<a-drawer :visible="visible" unmountOnClose :footer="false" :width="950" @cancel="onCancel">
<template #title>设置</template>

View File

@@ -136,9 +136,9 @@
<a-tooltip content="显隐搜索"
><a-button shape="circle" @click="toggleSearch"><icon-search /></a-button
></a-tooltip>
<a-tooltip content="打印表格"
<!-- <a-tooltip content="打印表格"
><a-button shape="circle" @click="printTable"><icon-printer /></a-button
></a-tooltip>
></a-tooltip> -->
<a-tooltip content="设置"
><a-button shape="circle" @click="tableSetting"><icon-settings /></a-button
></a-tooltip>
@@ -809,9 +809,9 @@ const tabChange = async (value) => {
await refresh()
}
const printTable = () => {
new Print(crudContentRef.value)
}
// const printTable = () => {
// new Print(crudContentRef.value)
// }
const openContextMenu = (ev, record) => {
options.value?.contextMenu?.enabled === true && crudContextMenuRef.value.openContextMenu(ev, record)
@@ -821,9 +821,9 @@ const execContextMenuCommand = async (args) => {
const item = args.contextItem
const record = args.record
switch (item.operation) {
case "print":
await printTable()
break
// case "print":
// await printTable()
// break
case "refresh":
await refresh()
break

View File

@@ -57,8 +57,11 @@ const props = defineProps({
type: [String, Array],
// 如果要取消粘贴只粘贴文本需要用户加格式请加上pastetext
default:
"code undo redo restoredraft | paste | bold | aligncenter alignleft alignjustify indent | \
styleselect formatselect fontselect fontsizeselect | bullist numlist | removeformat"
"undo redo aligncenter alignleft indent styleselect formatselect fontselect fontsizeselect removeformat"
// 下面是备份配置:
// default:"code undo redo restoredraft | paste | bold | aligncenter alignleft alignjustify indent | \
// styleselect formatselect fontselect fontsizeselect | bullist numlist | removeformat"
}
})

View File

@@ -0,0 +1,162 @@
<template>
<div class="chen-demand-list">
<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="20">
<span class="arco-table-cell arco-table-cell-align-center">
<a-tooltip content="添加步骤">
<a-button type="primary" size="mini" shape="round" @click="addItem">
新增步骤+
</a-button>
</a-tooltip>
</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">操作</span>
</span>
</th>
<th class="arco-table-th" :width="310">
<span class="arco-table-cell arco-table-cell-align-center">
<span class="arco-table-th-title">预期</span>
</span>
</th>
</tr>
</thead>
<tbody>
<!-- 根据subStep渲染 -->
<template
v-if="modelValue && modelValue.length > 0"
v-for="(stepItem, index) in modelValue"
:key="index"
>
<tr class="arco-table-tr">
<td class="arco-table-td">
<span class="arco-table-cell justify-center gap-1.5">
<!-- 删除单项按钮 -->
<a-button
type="primary"
status="danger"
size="mini"
shape="round"
@click="deleteItem(index)"
:disabled="modelValue.length <= 1"
>
<template #icon><icon-close /></template>
</a-button>
<a-tooltip content="复制该项添加">
<a-button
type="primary"
status="warning"
size="mini"
shape="round"
@click="copyItem(index)"
>
<template #icon>
<icon-copy />
</template>
</a-button>
</a-tooltip>
<a-button-group shape="round" size="mini">
<a-button type="primary" @click="moveUp(index)">
<icon-arrow-rise />
</a-button>
<a-button type="primary" @click="moveDown(index)">
<icon-arrow-fall />
</a-button>
</a-button-group>
</span>
</td>
<td class="arco-table-td">
<span class="arco-table-cell">
<a-textarea auto-size v-model="stepItem.operation"></a-textarea>
</span>
</td>
<td class="arco-table-td">
<span class="arco-table-cell">
<a-textarea auto-size v-model="stepItem.expect"></a-textarea>
</span>
</td>
</tr>
</template>
<template v-else>
<tr>
<td colspan="3">
<div class="flex justify-center items-center p-2 border-1">
<a-alert>暂无测试子项条目请添加</a-alert>
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { type Ref, nextTick } from "vue"
// 辅助函数当modelValue不是列表时候
function handleIsNotArray() {
if (Array.isArray(modelValue.value)) return
modelValue.value = []
}
// 定义value数据
interface ISubStep {
operation: string
expect: string
}
// 绑定外部数据预期是列表有operation、expect两个字段
const modelValue = defineModel() as unknown as Ref<ISubStep[]>
// 单纯新增 - modalValue新增一行
const addItem = async () => {
handleIsNotArray()
await nextTick() // 因为要等待modelValue值变化DOM渲染完毕
modelValue.value.push({ operation: "", expect: "" })
}
// 删除指定行
const deleteItem = (index: number) => {
modelValue.value = modelValue.value.filter((_, idx) => idx !== index)
}
// 复制单项
const copyItem = (index: number) => {
const newItem = modelValue.value[index]
modelValue.value = [...modelValue.value, newItem]
}
// 辅助函数交换列表两个元素
function swapItems(idx1: number, idx2: number) {
const arr = modelValue.value
;[arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]]
}
// 向下移动
const moveDown = (index: number) => {
if (index === modelValue.value.length - 1) return
swapItems(index, index + 1)
}
// 向上移动
const moveUp = (index: number) => {
if (index === 0) return
swapItems(index, index - 1)
}
</script>
<style lang="less" scoped>
:deep(.arco-form-item-content-flex) {
display: block;
}
:deep(.arco-table-cell .arco-form-item) {
margin-bottom: 0;
}
</style>

View File

@@ -1,7 +1,3 @@
<!--
- @Author XXX
- @Link XXX
-->
<template>
<a-form-item
v-if="typeof props.component.display == 'undefined' || props.component.display === true"
@@ -236,7 +232,7 @@ function swapItems(idx1, idx2) {
;[arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]]
}
// 修改源码上移动和下移动
// 修改源码上移动和下移动
const moveUp = (itemIndex) => {
const itemLength = formModel.value[props.component.dataIndex].length
// 如果是第一个,不做操作
@@ -290,9 +286,15 @@ onMounted(async () => {
})
</script>
<style scoped>
<style scoped lang="less">
:deep(.arco-form-item-content-flex) {
display: block;
position: relative;
.arco-radio-group {
position: absolute;
top: 50%;
transform: translateY(-40%);
}
}
:deep(.arco-table-cell .arco-form-item) {
margin-bottom: 0;

View File

@@ -1,7 +1,3 @@
<!--
- @Author XXX
- @Link XXX
-->
<template>
<td
v-if="typeof props.component?.display == 'undefined' || props.component?.display === true"

View File

@@ -1,7 +1,3 @@
<!--
- @Author XXX
- @Link XXX
-->
<template>
<table
v-if="typeof props.component?.display == 'undefined' || props.component?.display === true"

View File

@@ -1,7 +1,3 @@
<!--
- @Author XXX
- @Link XXX
-->
<template>
<ma-form-item
v-if="typeof props.component.display == 'undefined' || props.component.display === true"

View File

@@ -1,7 +1,3 @@
<!--
- @Author XXX
- @Link XXX
-->
<template>
<a-form-item
:label="props.component.title"

View File

@@ -341,7 +341,7 @@ const handleStdFormSubmit = async (data, done) => {
}
dictList.value["standard"].push(newInfo)
// 清空当前数据
stdFormData.value = initStdFormData
stdFormData.value = { ...initStdFormData }
Notification.success("添加成功,请回到输入框进行选择")
} catch (err) {
Notification.error("请求错误,请重试或在数据管理页面添加")

View File

@@ -0,0 +1,53 @@
<template>
<!-- 组件外部的 form-item -->
<ma-form-item
v-if="typeof props.component.display == 'undefined' || props.component.display === true"
:component="props.component"
:custom-field="props.customField"
>
<slot :name="`form-${props.component.dataIndex}`" v-bind="props.component">
<!-- 调用自己开发的自定义组件具体名称改成自己的 -->
<ChenDemandList v-model="value"></ChenDemandList>
</slot>
</ma-form-item>
</template>
<script setup>
// ~~~~start~~~~
import ChenDemandList from "../Customs/ChenDemandList.vue"
// ~~~~end~~~~
import { ref, inject, onMounted, watch } from "vue"
// 引入处理索引的函数
import { get, set } from "lodash-es"
// 引入 MaFormItem 组件
import MaFormItem from "./form-item.vue"
// 引入处理事件的函数
import { maEvent } from "../js/formItemMixin.js"
// 组件都需要定义以下的props
const props = defineProps({
component: Object,
customField: { type: String, default: undefined }
})
// form数据列表
const formModel = inject("formModel")
// 该组件在form数据的索引名称
const index = props.customField ?? props.component.dataIndex
// 该组件的表单数据
const value = ref(get(formModel.value, index))
// 监听组件数据的改变
watch(
() => get(formModel.value, index),
(vl) => (value.value = vl)
)
watch(
() => value.value,
(v) => set(formModel.value, index, v)
)
// 绑定组件事件
maEvent.handleCommonEvent(props.component, "onCreated")
onMounted(() => {
maEvent.handleCommonEvent(props.component, "onMounted")
})
</script>

View File

@@ -1,7 +1,3 @@
<!--
- @Author XXX
- @Link XXX
-->
<template>
<div class="w-full">
<a-spin :loading="formLoading" :tip="options.loadingText" class="w-full ma-form-spin">

View File

@@ -17,7 +17,7 @@
测试管理平台
</div>
</a-typography-title>
<a-typography-title :heading="6" class="version">V0.0.4</a-typography-title>
<a-typography-title :heading="6" class="version">v{{ $version }}</a-typography-title>
<icon-menu-fold
v-if="!topMenu && appStore.device === 'mobile'"
style="font-size: 22px; cursor: pointer"

View File

@@ -191,7 +191,7 @@
<!-- 关联的modal组件 -->
<a-modal v-model:visible="modalVisible" :width="700" draggable :on-before-ok="handleCopyDemand">
<template #title>复制到设计需求</template>
<div class="pb-3">选择复制到的节点:</div>
<div class="pb-3">选择复制到的节点<span class="point">支持搜索</span>:</div>
<a-cascader
:options="options"
allow-search
@@ -432,4 +432,7 @@ const {
.chen-node-title {
cursor: help;
}
.point {
color: red;
}
</style>

View File

@@ -48,11 +48,6 @@ export default function useRoundMaForm(projectId: Ref<string>, handleSoDutExists
}
]
},
{
title: "速度等级",
dataIndex: "speedGrade",
placeholder: "请填入速度等级"
},
{
title: "动态地点",
dataIndex: "location",
@@ -82,11 +77,6 @@ export default function useRoundMaForm(projectId: Ref<string>, handleSoDutExists
}
]
},
{
title: "封装",
dataIndex: "package",
placeholder: "请填入封装"
},
{
title: "质量等级",
dataIndex: "grade",
@@ -100,6 +90,7 @@ export default function useRoundMaForm(projectId: Ref<string>, handleSoDutExists
]
},
placeholder: "请填入质量等级",
addDefaultValue: "1",
rules: [{ required: true, message: "质量等级必填" }]
}
]
@@ -110,7 +101,7 @@ export default function useRoundMaForm(projectId: Ref<string>, handleSoDutExists
},
{
formType: "card",
title: "极端工况信息",
title: "极端工况信息(FPGA)",
customClass: ["mb-2", "pb-0"],
bodyStyle: { paddingBottom: 0 },
formList: [

View File

@@ -43,6 +43,7 @@ for (const path in modules) {
app.config.globalProperties.$tool = tool
app.config.globalProperties.$title = import.meta.env.VITE_APP_TITLE
app.config.globalProperties.$url = import.meta.env.VITE_APP_BASE
app.config.globalProperties.$version = packageJson.version
app.mount("#app")
// show version tag in console

View File

@@ -66,8 +66,9 @@ const useUserStore = defineStore("user", {
// login函数传一个form信息
login(form) {
const form_data = { username: form.username, password: form.password }
return loginAPI
.login(form)
.login(form_data)
.then((res) => {
if (res.success === true) {
this.setToken(res.data.token)

View File

@@ -59,6 +59,9 @@ function createService() {
case 500:
err("服务器内部错误")
break
case 400:
err("服务器抛出逻辑错误")
break
case 401:
err("登录状态已过期,需要重新登录")
// 清楚本地localStorage

View File

@@ -5,3 +5,15 @@ export interface IDictData<T> {
label: T
value: number
}
/**
* 产品文件的类型
*/
export type DocumentType =
| "测评大纲"
| "测试说明"
| "测试记录"
| "回归测试说明"
| "回归测试记录"
| "问题单"
| "测评报告"

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) => {
isComplete.value = true
visible.value = false
})
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: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" })
const blob = new Blob([st], {
type: fileType
})
const url = window.URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = `${docName}.docx` // 设置下载文件名
a.download = fileName // 设置文件名(动态)
a.click()
window.URL.revokeObjectURL(url) // 释放 URL 对象
// 上面是触发下载
isComplete.value = true
Message.success("文档生成并下载成功!")
} catch (err) {
isComplete.value = true
visible.value = false
}
}
}

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;

View File

@@ -44,7 +44,7 @@
"ES2015"
],
"types": [
"vite/client"
"vite/client",
]
},
"include": [