diff --git a/cdTMP/src/api/generate/dgGenerate.js b/cdTMP/src/api/generate/dgGenerate.js index 3453465..c769b25 100644 --- a/cdTMP/src/api/generate/dgGenerate.js +++ b/cdTMP/src/api/generate/dgGenerate.js @@ -101,7 +101,7 @@ export default { }, /** * - * @returns 生成测评大纲-被测软件性能 + * @returns 生成测评大纲-被测软件基本信息 */ createBaseInformation(params = {}) { return request({ @@ -110,6 +110,17 @@ export default { params }) }, + /** + * + * @returns 生成软硬件环境output_dir + */ + createEnvironment(params = {}) { + return request({ + url: `/generate/create/environment`, + method: "get", + params + }) + }, /** * * @returns 生成测评大纲-测试总体要求 @@ -209,4 +220,15 @@ export default { params }) }, + /** + * + * @returns 生成-主要战技指标 + */ + createMainTech(params = {}) { + return request({ + url: `/generate/create/mainTech`, + method: "get", + params + }) + } } diff --git a/cdTMP/src/api/generate/dgSeitaiGenerate.js b/cdTMP/src/api/generate/seitaiGenerate.js similarity index 52% rename from cdTMP/src/api/generate/dgSeitaiGenerate.js rename to cdTMP/src/api/generate/seitaiGenerate.js index 66b9c36..2e6fefd 100644 --- a/cdTMP/src/api/generate/dgSeitaiGenerate.js +++ b/cdTMP/src/api/generate/seitaiGenerate.js @@ -10,5 +10,16 @@ export default { method: "get", params }) + }, + /** + * 如果缺少部分文件给与提示 + * @returns 根据output_dir以及output_dir/sm中文档生成测试说明 + */ + createShuomingSeiTai(params = {}) { + return request({ + url: `/create/smDocument`, + method: "get", + params + }) } } diff --git a/cdTMP/src/api/generate/smGenerate.js b/cdTMP/src/api/generate/smGenerate.js new file mode 100644 index 0000000..04d5c0c --- /dev/null +++ b/cdTMP/src/api/generate/smGenerate.js @@ -0,0 +1,47 @@ +import { request } from "@/api/request" +export default { + /** + * + * @returns 大纲测试项数据 + */ + createSMTechyiju(params = {}) { + return request({ + url: `/generateSM/create/techyiju`, + method: "get", + params + }) + }, + /** + * + * @returns 生成测试用例 + */ + createSMCaseList(params = {}) { + return request({ + url: `/generateSM/create/caseList`, + method: "get", + params + }) + }, + /** + * + * @returns 生成测试用例那个表-测试名称/标识/测试用例综述 + */ + createSMCaseBreifList(params = {}) { + return request({ + url: `/generateSM/create/caseBreifList`, + method: "get", + params + }) + }, + /** + * + * @returns 生成说明追踪 + */ + createSMTrack(params = {}) { + return request({ + url: `/generateSM/create/smtrack`, + method: "get", + params + }) + } +} diff --git a/cdTMP/src/views/testmanage/projmanage/index.vue b/cdTMP/src/views/testmanage/projmanage/index.vue index b5a39c1..2f135e1 100644 --- a/cdTMP/src/views/testmanage/projmanage/index.vue +++ b/cdTMP/src/views/testmanage/projmanage/index.vue @@ -6,12 +6,10 @@ @@ -25,7 +23,8 @@ import { useRoute, useRouter } from "vue-router" import projectApi from "@/api/testmanage/project" import preview from "./cpns/preview.vue" import dgGenerateApi from "@/api/generate/dgGenerate" -import dgSeitaiGenerateApi from "@/api/generate/dgSeitaiGenerate" +import seitaiGenerateApi from "@/api/generate/seitaiGenerate" +import smGenerateApi from "@/api/generate/smGenerate" import { Message } from "@arco-design/web-vue" import Progress from "./cpns/progress.vue" const router = useRouter() @@ -44,13 +43,20 @@ const isComplete = ref(false) const handleModalConfirmClick = () => { visible.value = false } -// ~~~~~~~~测试生成文档~~~~~~~~ +// ~~~~~~~~测试说明生成文档~~~~~~~~ +const createSeitaiShuoming = async (record) => { + const st = await seitaiGenerateApi.createShuomingSeiTai({ id: record.id }) + + Message.success(st.message) +} + +// ~~~~~~~~测试大纲生成文档~~~~~~~~ const createSeitaiDagang = async (record) => { // 根据一系列文档生成大纲 - 这里有进度条组件、a-modal组件 // 1.打开进度条组件 visible.value = true isComplete.value = false - const st = await dgSeitaiGenerateApi.createDagangSeiTai({ id: record.id }).catch((err) => { + const st = await seitaiGenerateApi.createDagangSeiTai({ id: record.id }).catch((err) => { isComplete.value = true visible.value = false }) @@ -58,15 +64,39 @@ const createSeitaiDagang = async (record) => { Message.success(st.message) } -const createItem = async (record) => { +// 说明生成二级文档 +const createSmItem = async (record) => { + // 生成测评对象 - 和大纲一样 - 可能会删除 + const st = await dgGenerateApi.createSoftComposition({ id: record.id }) + // 生成被测软件功能 - 和大纲重复 - 可能会删除 + const st1 = await dgGenerateApi.createFuncList({ id: record.id }) + // 生成被测软件接口 - 和大纲重复 - 可能会删除 + const st2 = await dgGenerateApi.createInterface({ id: record.id }) + // 生成被测软件性能 - 和大纲重复 - 可能会删除 + const st3 = await dgGenerateApi.createPerformance({ id: record.id }) + // 生成被测软件基本信息 - 和大纲重复 - 可能会删除 + const st4 = await dgGenerateApi.createBaseInformation({ id: record.id }) + // 生成标准类引用文档 - 和大纲重复 - 可能会删除 + const st5 = await dgGenerateApi.createYiju({ id: record.id }) + // 生成技术类引用文档列表 -> 在大纲基础上添加《测评大纲》 + const st6 = await smGenerateApi.createSMTechyiju({ id: record.id }) + // 生成软硬件环境(注意标题级别不一样,这个在最后处理) + const st7 = await dgGenerateApi.createEnvironment({ id: record.id }) + // 生成用例全 + const st8 = await smGenerateApi.createSMCaseList({ id: record.id }) + // 生成用例列表-那个表格 + const st9 = await smGenerateApi.createSMCaseBreifList({ id: record.id }) + // 生成说明追踪 + const st10 = await smGenerateApi.createSMTrack({ id: record.id }) + + Message.success(st10.message) +} +// 大纲生成二级文档 +const createDgItem = async (record) => { // 生成测试项文档 const st = await dgGenerateApi.createTestDemand({ id: record.id }) - Message.success(st.message) -} - -const createYiju = async (record) => { // 标准依据文件 - const st = await dgGenerateApi.createYiju({ id: record.id }) + const st1 = await dgGenerateApi.createYiju({ id: record.id }) // 技术依据文件 const st2 = await dgGenerateApi.createTechYiju({ id: record.id }) // 生成时间和地点 @@ -75,58 +105,37 @@ const createYiju = async (record) => { const st4 = await dgGenerateApi.createFuncList({ id: record.id }) // 生成测评对象-软件组成 const st5 = await dgGenerateApi.createSoftComposition({ id: record.id }) - Message.success(st.message) - Message.success(st2.message) - Message.success(st3.message) - Message.success(st4.message) - Message.success(st5.message) -} -const createContact = async (record) => { // 生成联系人和方式 - const st = await dgGenerateApi.createContact({ id: record.id }) + const st6 = await dgGenerateApi.createContact({ id: record.id }) // 生成测试充分性(adequancy)和有效性(effectiveness)说明 - const st2 = await dgGenerateApi.createAdequacyEffectiveness({ id: record.id }) + const st7 = await dgGenerateApi.createAdequacyEffectiveness({ id: record.id }) // 生成测评组织及分工 - const st3 = await dgGenerateApi.createGroup({ id: record.id }) + const st8 = await dgGenerateApi.createGroup({ id: record.id }) // 生成测评保障 - const st4 = await dgGenerateApi.createGuarantee({ id: record.id }) + const st9 = await dgGenerateApi.createGuarantee({ id: record.id }) // 生成缩略语 - const st5 = await dgGenerateApi.createAbbreviation({ id: record.id }) - Message.success(st.message) - Message.success(st2.message) - Message.success(st3.message) - Message.success(st4.message) - Message.success(st5.message) -} - -const createInter = async (record) => { + const st10 = await dgGenerateApi.createAbbreviation({ id: record.id }) // 生成-被测软件接口 - const st = await dgGenerateApi.createInterface({ id: record.id }) + const st11 = await dgGenerateApi.createInterface({ id: record.id }) // 生成-被测软件性能 - const st2 = await dgGenerateApi.createPerformance({ id: record.id }) + const st12 = await dgGenerateApi.createPerformance({ id: record.id }) // 生成-被测软件基本信息 - const st3 = await dgGenerateApi.createBaseInformation({ id: record.id }) + const st13 = await dgGenerateApi.createBaseInformation({ id: record.id }) // 生成-测试总体要求 - const st4 = await dgGenerateApi.createRequirement({ id: record.id }) - Message.success(st.message) - Message.success(st2.message) - Message.success(st3.message) - Message.success(st4.message) -} - -const createZhuiZ = async (record) => { + const st14 = await dgGenerateApi.createRequirement({ id: record.id }) // 生成-研总-测试项对照表 - const st = await dgGenerateApi.createYzComparison({ id: record.id }) + const st15 = await dgGenerateApi.createYzComparison({ id: record.id }) // 生成-需求规格说明-测试项对照表 - const st2 = await dgGenerateApi.createXqComparison({ id: record.id }) + const st16 = await dgGenerateApi.createXqComparison({ id: record.id }) // 生成-反向测试项-需求规格说明对照表 - const st3 = await dgGenerateApi.createFanXqComparison({ id: record.id }) + const st17 = await dgGenerateApi.createFanXqComparison({ id: record.id }) // 生成-代码质量度量分析表 - const st4 = await dgGenerateApi.createCodeQuality({ id: record.id }) - Message.success(st.message) - Message.success(st2.message) - Message.success(st3.message) - Message.success(st4.message) + const st18 = await dgGenerateApi.createCodeQuality({ id: record.id }) + // 生成-软硬件环境 + const st19 = await dgGenerateApi.createEnvironment({ id: record.id }) + // 生成-主要战技指标 + const st20 = await dgGenerateApi.createMainTech({ id: record.id }) + Message.success(st20.message) } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -538,3 +547,4 @@ const crudColumns = ref([ } } +@/api/generate/seitaiGenerate diff --git a/cdTMP/temp.py b/cdTMP/temp.py new file mode 100644 index 0000000..065331a --- /dev/null +++ b/cdTMP/temp.py @@ -0,0 +1,154 @@ +from ninja_extra import api_controller, route, ControllerBase +from django.db import transaction +from pathlib import Path +from django.shortcuts import get_object_or_404 +# 文档处理相关库 +from docxtpl import DocxTemplate +from docx import Document +from docx.text.paragraph import Paragraph +from docx.table import Table +from docx.oxml.table import CT_Tbl +from docx.oxml.text.paragraph import CT_P +from docx.oxml.text.run import CT_R +from docx.oxml.shape import CT_Picture +from docx.parts.image import ImagePart +from docx.text.run import Run +from docx.shared import Cm +from docx.enum.text import WD_PARAGRAPH_ALIGNMENT + +@route.get('/smDocument', url_name='create-smDocument') +@transaction.atomic +def create_smDocument(self, id: int): + """生成最后说明文档""" + # 获取项目对象 + project_obj = get_object_or_404(Project, id=id) + # 首先第二层模版所需变量 + member = project_obj.member[0] if len(project_obj.member) > 0 else project_obj.duty_person + context = {'name': project_obj.name, 'is_JD': False, 'ident': project_obj.ident, 'sec_title': "公开", + 'duty_person': project_obj.duty_person, 'member': member} + if project_obj.report_type == '9': + context['is_JD'] = True + # 提取第一轮测试中源代码 - 用户标识 + round_1 = project_obj.pField.filter(key='0').first() + duty_so = round_1.rdField.filter(type='SO').first() + if not duty_so: + return ChenResponse(code=400, status=400, message="未找到第一轮测试中源代码被测件请添加") + context['user_ident'] = duty_so.ref + + # ~~~~~~定义模版路径~~~~~~~ + ## 定义说明最后模版 + sm_template_file = Path.cwd() / 'media' / 'form_template' / 'products' / '测试说明.docx' + # docx生成后给docxtpl文件路径 + sm_to_tpl_file = Path.cwd() / 'media' / 'temp' / '测试说明.docx' + # 生成最后文档路径 + sm_seitai_final_file = Path.cwd() / 'media' / 'final_seitai' / '测试说明.docx' + # output_dir根路径 + output_files_path = Path.cwd() / 'media' / 'output_dir' + # 获取可能使用的被复制的docx文件 + dg_copied_files = [] + sm_copied_files = [] + for file in output_files_path.iterdir(): + if file.is_file(): + if file.suffix == '.docx': + dg_copied_files.append(file) + elif file.is_dir(): + if file.stem == 'sm': + for f in file.iterdir(): + if f.suffix == '.docx': + sm_copied_files.append(f) + # 打开文档找到所有的“域” + doc = Document(sm_template_file.as_posix()) + body = doc.element.body + sdt_element_list = body.xpath('./w:sdt') + # 找到sdt域的名称 -> 为了对应output_dir文件 / 储存所有output_dir图片 + area_name_list = [] + image_part_list = [] + # 遍历所有控件 -> 放入area_name_list【这里准备提取公共代码】 + for sdt_ele in sdt_element_list: + for elem in sdt_ele.iterchildren(): + # 获取“域”的名称 + if elem.tag.endswith('sdtPr'): + for el in elem.getchildren(): + if el.tag.endswith('alias'): + if len(el.attrib.values()) > 0: + area_name = el.attrib.values()[0] + area_name_list.append(area_name) + # 开始替换里面的“域” + if elem.tag.endswith('sdtContent'): + elem.clear() + if len(area_name_list) > 0: + area_pop_name = area_name_list.pop(0) + # 取到“域名称”,这里先去找media/output_dir/sm下文件,然后找media/output下文件 + copied_file_path = "" + for file in sm_copied_files: + if file.stem == area_pop_name: + copied_file_path = file + # 这里判断是否copied_file_path没取到文件,然后遍历output_dir下文件 + if not copied_file_path: + for file in dg_copied_files: + if file.stem == area_pop_name: + copied_file_path = file + # 找到所需的文件,将其数据复制到对应area_name的“域” + if copied_file_path: + doc_copied = Document(copied_file_path) + copied_element_list = [] + element_list = doc_copied.element.body.inner_content_elements + for elet in element_list: + if isinstance(elet, CT_P): + copied_element_list.append(Paragraph(elet, doc_copied)) + if isinstance(elet, CT_Tbl): + copied_element_list.append(Table(elet, doc_copied)) + for para_copied in copied_element_list: + elem.append(para_copied._element) + + # 下面代码就是将图片全部提取到image_part_list,以便后续插入 + doc_copied = Document(copied_file_path) # 需要重新获取否则namespace错误 + copied_body = doc_copied.element.body + img_node_list = copied_body.xpath('.//pic:pic') + if not img_node_list: + pass + else: + for img_node in img_node_list: + img: CT_Picture = img_node + # 根据节点找到图片的关联id + embed = img.xpath('.//a:blip/@r:embed')[0] + # 这里得到ImagePart -> 马上要给新文档添加 + related_part: ImagePart = doc_copied.part.related_parts[embed] + # doc_copied.part.related_parts是一个字典 + image_part_list.append(related_part) + + # 新文档添加sdt_element_list的图片 + graph_node_list = body.xpath('.//pic:pic') + img_count = 0 + if len(graph_node_list) == len(image_part_list): + for graph_node in graph_node_list: + image_run_node = getParentRunNode(graph_node) + image_run_node.clear() + copied_bytes_io = BytesIO(image_part_list[img_count].image.blob) + r_element = Run(image_run_node, doc) + inline_shape = r_element.add_picture(copied_bytes_io) + # 设置图片位置尺寸 + source_width = inline_shape.width + inline_shape.width = Cm(12) + inline_shape.height = int(inline_shape.height * (inline_shape.width / source_width)) + # 设置图片所在段落居中对齐 + r_element.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER + img_count += 1 + else: + return ChenResponse(code=400, status=400, message='注意模版里面有自定义图片,请删除后重试!!!') + + try: + doc.save(sm_to_tpl_file) + except PermissionError as e: + return ChenResponse(status=400, code=400, message="注意你打开了生成的文档,请关闭后再试,{0}".format(e)) + + doc = DocxTemplate(sm_to_tpl_file) + start = time.time() + doc.render(context) # 耗时最长,TODO:异步任务处理?或前端等待? + end = time.time() + print('渲染耗时:', end - start) + try: + doc.save(sm_seitai_final_file) + return ChenResponse(status=200, code=200, message="最终大纲生成成功!") + except PermissionError as e: + return ChenResponse(status=400, code=400, message="模版文件已打开,请关闭后再试,{0}".format(e))