需求解析功能进一步完善
This commit is contained in:
@@ -10,7 +10,44 @@ export default function useOptions(formRef: any) {
|
||||
})
|
||||
const crudColumns = useColumn(formRef)
|
||||
const columnOptions = computed(() => {
|
||||
return tool.renameKeyInArray(crudColumns.value, "commonRules", "rules")
|
||||
const transformColumns = tool.renameKeyInArray(crudColumns.value, "commonRules", "rules")
|
||||
// 针对测试项的布局优化
|
||||
// 取出所有字段
|
||||
const identColumn = transformColumns.find((item: any) => item.dataIndex === "ident")
|
||||
const nameColumn = transformColumns.find((item: any) => item.dataIndex === "name")
|
||||
const priorityColumn = transformColumns.find((item: any) => item.dataIndex === "priority")
|
||||
const testTypeColumn = transformColumns.find((item: any) => item.dataIndex === "testType")
|
||||
const testMethodColumn = transformColumns.find((item: any) => item.dataIndex === "testMethod")
|
||||
// 组装表单布局
|
||||
const identAndNameColumn = {
|
||||
formType: "grid",
|
||||
cols: [
|
||||
{ span: 12, formList: [identColumn] },
|
||||
{ span: 12, formList: [nameColumn] }
|
||||
]
|
||||
}
|
||||
const priorityColumnNew = {
|
||||
formType: "grid",
|
||||
cols: [{ span: 24, formList: [priorityColumn] }]
|
||||
}
|
||||
const testTypeAndMethodColumn = {
|
||||
formType: "grid",
|
||||
cols: [
|
||||
{ span: 12, formList: [testTypeColumn] },
|
||||
{ span: 12, formList: [testMethodColumn] }
|
||||
]
|
||||
}
|
||||
// 取除原数组里面的内容
|
||||
const newColumnsArray = transformColumns.filter(
|
||||
(it: any) =>
|
||||
it.dataIndex !== "ident" &&
|
||||
it.dataIndex !== "name" &&
|
||||
it.dataIndex !== "priority" &&
|
||||
it.dataIndex !== "testType" &&
|
||||
it.dataIndex !== "testMethod"
|
||||
)
|
||||
newColumnsArray.unshift(identAndNameColumn, priorityColumnNew, testTypeAndMethodColumn)
|
||||
return newColumnsArray
|
||||
})
|
||||
return { options, columnOptions }
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function (crudOrFormRef: any) {
|
||||
{
|
||||
title: "名称",
|
||||
dataIndex: "name",
|
||||
width: 120,
|
||||
width: 150,
|
||||
align: "center",
|
||||
search: true,
|
||||
commonRules: [{ required: true, message: "名称是必填" }],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<a-modal
|
||||
v-model:visible="modalVisible"
|
||||
width="80%"
|
||||
title="设计需求批量写入"
|
||||
title="批量导入设计需求"
|
||||
:unmount-on-close="true"
|
||||
:mask-closable="false"
|
||||
ok-text="录入"
|
||||
@@ -11,20 +11,30 @@
|
||||
:on-before-ok="handleModalSubmit"
|
||||
>
|
||||
<div class="uploadContainer">
|
||||
<span :style="{ marginBottom: '10px', flex: '0 1 160px' }">上传设计需求.docx:</span>
|
||||
<span :style="{ marginBottom: '10px', flex: '0 1 150px' }">上传需求.docx:</span>
|
||||
<a-upload
|
||||
:style="{ marginBottom: '10px' }"
|
||||
:custom-request="handleRquest"
|
||||
:limit="1"
|
||||
accept=".docx"
|
||||
:action="`/api/dut_upload/upload_xq_docx/?parseChapter=${parseChapter}`"
|
||||
@change="handleUploadChange"
|
||||
@success="handleUploadSuccess"
|
||||
@error="handleUploadError"
|
||||
:disabled="parseChapter.trim() === ''"
|
||||
></a-upload>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="w-[350px]">要解析的章节名称:</span>
|
||||
<a-input placeholder="输入要解析的章节名称" v-model="parseChapter"></a-input>
|
||||
<span class="w-[350px]">选择需求录入类型:</span>
|
||||
<a-select allow-search v-model="selectValue">
|
||||
<a-option v-for="item in demandType" :key="item.key" :value="item.key">{{ item.title }}</a-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<a-alert :style="{ margin: '10px 0' }" type="warning">
|
||||
请去除需求规格说明文件中不必要的部分再来此处解析,注意:本系统不支持word中emf格式图片,如果使用了visio等图片请<span
|
||||
class="important-text"
|
||||
>在word变为普通图片上传</span
|
||||
>
|
||||
只能上传.docx,<span class="important-text">如果有visio图请替换为普通图片上传</span
|
||||
>,请在需求规格说明文档中操作 ->
|
||||
<span class="important-text">引用 -> 目录 -> 自定义目录 -> 显示级别改为6</span>以上保存后上传
|
||||
</a-alert>
|
||||
<div class="operation-container">
|
||||
<span :style="{ marginRight: '10px', fontWeight: 700 }">操作按钮:</span>
|
||||
@@ -109,104 +119,39 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, nextTick, watch } from "vue"
|
||||
import mammoth from "mammoth"
|
||||
import { ref, watch } from "vue"
|
||||
import dictApi from "@/api/system/dict"
|
||||
import demandApi from "@/api/project/designDemand"
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { parseHtmlStringByDemandDut } from "@/views/project/dut/tools/parseHtmlString"
|
||||
import { HtmlParser } from "@/views/project/dut/tools/parser"
|
||||
import { useRoute } from "vue-router"
|
||||
import { useTreeDataStore } from "@/store"
|
||||
// 其他初始化数据
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const treeDataStore = useTreeDataStore()
|
||||
// ~导入editor组件~tinymce
|
||||
import MaEditor from "@/components/ma-editor/index.vue"
|
||||
import { Message } from "@arco-design/web-vue"
|
||||
// 单个设计需求录入模版
|
||||
const templateDemandObj = {
|
||||
chapter: "",
|
||||
title: "",
|
||||
ident: "",
|
||||
demandType: "",
|
||||
content: ""
|
||||
}
|
||||
// 先请求设计需求的dict,然后给demandType选项
|
||||
import useUpload from "./useUpload"
|
||||
import useListOperaton from "./useListOperation"
|
||||
// 定义录入完毕的事件,给父组件刷新表格
|
||||
const emit = defineEmits(["enterFinish"])
|
||||
// 其他初始化数据
|
||||
const route = useRoute()
|
||||
const treeDataStore = useTreeDataStore()
|
||||
// ~~~刚开始就加载字典数据,给选择框使用~~~
|
||||
let demandType = []
|
||||
const getDictDemandType = async function () {
|
||||
const res = await dictApi.getDictByCode({ code: "demandType" })
|
||||
demandType = res.data
|
||||
}
|
||||
getDictDemandType()
|
||||
// 弹窗显示
|
||||
// 弹窗显示ref
|
||||
const modalVisible = ref(false)
|
||||
// 全部html数据-循环创建折叠项
|
||||
// 全部html数据-给a-list展示
|
||||
const htmlData = ref([])
|
||||
// 数据变化spin显示
|
||||
const loading = ref(false)
|
||||
// 当upload组件发生变化时候
|
||||
const handleUploadChange = (files) => {
|
||||
files.length ? (files.length = 1) : (htmlData.value = [])
|
||||
}
|
||||
// 上传行为函数
|
||||
const handleRquest = function (options) {
|
||||
const { onProgress, onError, onSuccess, fileItem } = options
|
||||
onProgress(0.1)
|
||||
// 让spin组件先转圈
|
||||
loading.value = true
|
||||
// 让Empty组件不显示
|
||||
htmlData.value.push(templateDemandObj)
|
||||
// 获取文件
|
||||
let reader = new FileReader()
|
||||
reader.readAsArrayBuffer(fileItem.file)
|
||||
reader.onload = function (loadEvent) {
|
||||
let arrayBuffer = loadEvent.target.result
|
||||
mammoth
|
||||
.convertToHtml({ arrayBuffer: arrayBuffer })
|
||||
.then((res) => {
|
||||
// 已经上传到浏览器了,需要解析为列表
|
||||
onSuccess(1)
|
||||
const rawHtml = res.value
|
||||
const parser = new HtmlParser(rawHtml)
|
||||
const finalData = parser.parseToArray()
|
||||
htmlData.value = finalData
|
||||
// ~~~~使用nextTick:等待DOM更新完成~~~~
|
||||
nextTick(() => {
|
||||
loading.value = false
|
||||
})
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log("处理错误导致失败,请检查前端代码")
|
||||
console.log("错误如下:", error)
|
||||
onError(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
// 上方按钮:直接在最下新增一条
|
||||
const handleCreateAtLatest = () => {
|
||||
const newDemand = JSON.parse(JSON.stringify(templateDemandObj))
|
||||
htmlData.value.push(newDemand)
|
||||
}
|
||||
// 上方按钮:重置数据,点击页面不卡段
|
||||
const handleResetData = () => {
|
||||
htmlData.value = []
|
||||
}
|
||||
// 点击单条右侧按钮:下方新增 - 深拷贝,并插入到下方
|
||||
const handledownCreate = (index) => {
|
||||
const newDemand = JSON.parse(JSON.stringify(templateDemandObj))
|
||||
htmlData.value.splice(index + 1, 0, newDemand)
|
||||
}
|
||||
// 因为a-list限制必须知道当前页码和页容量
|
||||
const currentPage = ref(1)
|
||||
const handlePageChange = (page) => {
|
||||
currentPage.value = page
|
||||
}
|
||||
// 点击单条右侧按钮:删除 - 需要根据currentPage动态觉得因为a-list每页都是这样计算的
|
||||
const handleDelete = (index) => {
|
||||
const currentIndex = index + (currentPage.value - 1) * 15
|
||||
htmlData.value.splice(currentIndex, 1)
|
||||
}
|
||||
|
||||
// ~~~~1.list~~~~
|
||||
const { loading, handleCreateAtLatest, handleResetData, handledownCreate, handlePageChange, handleDelete } =
|
||||
useListOperaton(htmlData)
|
||||
|
||||
// ~~~~2.upload~~~~
|
||||
const { handleUploadSuccess, handleUploadError, parseChapter, selectValue } = useUpload(htmlData)
|
||||
|
||||
// 打开弹窗并初始化form数据
|
||||
const open = function () {
|
||||
// 打开时候传入对象可初始化from
|
||||
@@ -234,11 +179,19 @@ const handleModalSubmit = async () => {
|
||||
})
|
||||
if (res.code === 200) {
|
||||
treeDataStore.updateDesignDemandTreeData(res.data, route.query.id)
|
||||
// 给父元素说明我已经完成了
|
||||
emit("enterFinish")
|
||||
Message.success("批量新增设计需求成功...")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 当upload组件发生变化时候-性能配置
|
||||
const handleUploadChange = (files) => {
|
||||
files.length ? (files.length = 1) : (htmlData.value = [])
|
||||
}
|
||||
|
||||
// 暴露该组件ref的方法
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ref } from "vue"
|
||||
|
||||
const templateDemandObj = {
|
||||
chapter: "",
|
||||
title: "",
|
||||
ident: "",
|
||||
demandType: "",
|
||||
content: ""
|
||||
}
|
||||
|
||||
export default function useListOperaton(htmlData: any) {
|
||||
// 数据变化spin显示
|
||||
const loading = ref(false)
|
||||
// 上方按钮:直接在最下新增一条
|
||||
const handleCreateAtLatest = () => {
|
||||
const newDemand = JSON.parse(JSON.stringify(templateDemandObj))
|
||||
htmlData.value.push(newDemand)
|
||||
}
|
||||
// 上方按钮:重置数据,点击页面不卡段
|
||||
const handleResetData = () => {
|
||||
htmlData.value = []
|
||||
}
|
||||
// 点击单条右侧按钮:下方新增 - 深拷贝,并插入到下方
|
||||
const handledownCreate = (index: number) => {
|
||||
const newDemand = JSON.parse(JSON.stringify(templateDemandObj))
|
||||
htmlData.value.splice(index + 1, 0, newDemand)
|
||||
}
|
||||
// 因为a-list限制必须知道当前页码和页容量
|
||||
const currentPage = ref(1)
|
||||
const handlePageChange = (page) => {
|
||||
currentPage.value = page
|
||||
}
|
||||
// 点击单条右侧按钮:删除 - 需要根据currentPage动态觉得因为a-list每页都是这样计算的
|
||||
const handleDelete = (index: number) => {
|
||||
const currentIndex = index + (currentPage.value - 1) * 15
|
||||
htmlData.value.splice(currentIndex, 1)
|
||||
}
|
||||
return { loading, handleCreateAtLatest, handleResetData, handledownCreate, handlePageChange, handleDelete }
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { ref } from "vue"
|
||||
import { Message, Notification } from "@arco-design/web-vue"
|
||||
|
||||
const templateDemandObj = {
|
||||
chapter: "",
|
||||
title: "",
|
||||
ident: "",
|
||||
demandType: "",
|
||||
content: ""
|
||||
}
|
||||
// 判断是否为{"__type__": "image","format": "base64","data": "base64数据"}
|
||||
function isImageObject(obj: any) {
|
||||
return (
|
||||
obj !== null &&
|
||||
typeof obj === "object" &&
|
||||
!Array.isArray(obj) &&
|
||||
Object.hasOwn(obj, "__type__") &&
|
||||
Object.hasOwn(obj, "format") &&
|
||||
Object.hasOwn(obj, "data")
|
||||
)
|
||||
}
|
||||
|
||||
export default function useUpload(htmlData: any) {
|
||||
const parseChapter = ref("") // 定义用户想解析的章节名称
|
||||
const selectValue = ref("1") // 定义选择框
|
||||
const handleUploadSuccess = (fileItem: any) => {
|
||||
const data = fileItem.response.data
|
||||
if (!data.children) {
|
||||
Notification.error({
|
||||
content: "解析失败:请确认上传文件目录级别包含6以上;章节名称正确",
|
||||
duration: 3000,
|
||||
closable: true
|
||||
})
|
||||
parseChapter.value = ""
|
||||
return
|
||||
}
|
||||
// 这里就是解析出东西了
|
||||
const parsedData = getObjFromChapter(data)
|
||||
if (parsedData) {
|
||||
enterDemand(parsedData)
|
||||
}
|
||||
// 上传成功后清空解析章节名称
|
||||
parseChapter.value = ""
|
||||
}
|
||||
const handleUploadError = (fileItem: any) => {
|
||||
console.log(fileItem.response)
|
||||
}
|
||||
const getObjFromChapter = (parseObj: any) => {
|
||||
if (parseObj.title === parseChapter.value) return parseObj
|
||||
if (Array.isArray(parseObj.children) && parseObj.children.length > 0) {
|
||||
for (const child of parseObj.children) {
|
||||
const found = getObjFromChapter(child)
|
||||
if (found) return found // 递归返回obj或null
|
||||
}
|
||||
}
|
||||
return null // 如果都没找到返回null
|
||||
}
|
||||
// 辅助函数:(递归)将parseObj转为templateDemandObj录入,递归录入
|
||||
const enterDemand = (parseObj: any) => {
|
||||
// 下面是录入
|
||||
const demandObj = JSON.parse(JSON.stringify(templateDemandObj))
|
||||
demandObj.chapter = parseObj.number
|
||||
demandObj.title = parseObj.title
|
||||
demandObj.ident = parseObj.ordinal ? parseObj.ordinal : "" // 设计需求标识如果没有则为空
|
||||
demandObj.demandType = selectValue.value
|
||||
// 解析数组,然后添加到内容中
|
||||
const content = formatContentForTinyMCE(JSON.parse(parseObj.content))
|
||||
demandObj.content = content
|
||||
htmlData.value.push(demandObj)
|
||||
// 如果有子对象,则再运行一次即可
|
||||
if (Array.isArray(parseObj.children) && parseObj.children.length > 0) {
|
||||
for (const childObj of parseObj.children) {
|
||||
enterDemand(childObj)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 上面函数的辅助函数:将数组变为HTML给tinymce使用
|
||||
function formatContentForTinyMCE(data: any) {
|
||||
let htmlContent = ""
|
||||
// 处理普通文本行
|
||||
for (const item of data) {
|
||||
// 如果是普通文本
|
||||
if (typeof item === "string") {
|
||||
// 处理表头标记
|
||||
if (item.includes("见表") || item.includes("如表") || item.includes("如下表")) {
|
||||
htmlContent += `<p>${item.replace(/见表\d+/, "见下表").replace(/见图\d+/, "见下图")}</p>`
|
||||
} else if (item.includes("见图") || item.includes("如图") || item.includes("如下图")) {
|
||||
htmlContent += `<p>${item.replace(/见图\d+/, "见下图").replace(/见表\d+/, "见下表")}</p>`
|
||||
} else if (/表\d+/.test(item)) {
|
||||
htmlContent += ``
|
||||
} else if (/图\d+/.test(item)) {
|
||||
htmlContent += `${item.replace(/图\d+/, "下图")}`
|
||||
} else {
|
||||
htmlContent += `<p>${item}</p>`
|
||||
}
|
||||
}
|
||||
// 如果是对象(根据后端逻辑是图片)
|
||||
else if (isImageObject(item)) {
|
||||
// item现在是对象,其data是base64字符串
|
||||
htmlContent += `<p><img src="data:image/png;base64,${item.data}" style="max-width:100%; height:auto; margin: 0 auto; display: block;"></img></p>`
|
||||
}
|
||||
// 处理表格数据
|
||||
else if (Array.isArray(item)) {
|
||||
htmlContent += '<table border="1" style="margin: 0 auto;max-width:70%; border-collapse:collapse;">'
|
||||
// 处理表头
|
||||
const headers = item[0].split("\t")
|
||||
htmlContent += "<thead><tr>"
|
||||
for (const header of headers) {
|
||||
htmlContent += `<th style="padding:8px; border:1px solid #ddd;">${header}</th>`
|
||||
}
|
||||
htmlContent += "</tr></thead>"
|
||||
// 处理表格内容
|
||||
htmlContent += "<tbody>"
|
||||
for (let i = 1; i < item.length; i++) {
|
||||
const cells = item[i].split("\t")
|
||||
htmlContent += "<tr>"
|
||||
for (const cell of cells) {
|
||||
// 处理单元格内的换行符
|
||||
const formattedCell = cell.replace(/\n/g, "<br>")
|
||||
htmlContent += `<td style="padding:8px; border:1px solid #ddd;">${formattedCell}</td>`
|
||||
}
|
||||
htmlContent += "</tr>"
|
||||
}
|
||||
htmlContent += "</tbody></table>"
|
||||
}
|
||||
}
|
||||
return htmlContent
|
||||
}
|
||||
return { handleUploadSuccess, handleUploadError, parseChapter, selectValue }
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
<template #inputPrepend-ident> SJ-XX- </template>
|
||||
</ma-crud>
|
||||
</div>
|
||||
<file-input-modal ref="fileInputRef"></file-input-modal>
|
||||
<file-input-modal ref="fileInputRef" @enterFinish="crudRef.refresh()"></file-input-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
/**
|
||||
* 辅助函数,给每个h2增加个自定义属性,用来储存章节号,返回功能/接口h2节点对象【非DOM,有dom属性表示DOM】
|
||||
*/
|
||||
function getNeedH2NodeList(h1h2Node) {
|
||||
const h2ObjList = []
|
||||
// 首先将h1和h2节点都组成一个数组
|
||||
let h1Index = 0,
|
||||
h2Index = 0
|
||||
h1h2Node.forEach((hDom) => {
|
||||
if (hDom.tagName === "H1") {
|
||||
// 找到h1,那么其index+1
|
||||
h1Index += 1
|
||||
h2Index = 0
|
||||
} else if (hDom.tagName === "H2") {
|
||||
// 按顺序找到h2了,
|
||||
h2Index += 1
|
||||
let h2Obj = {
|
||||
chapter: h1Index + "." + h2Index,
|
||||
dom: hDom,
|
||||
text: hDom.innerText
|
||||
}
|
||||
h2ObjList.push(h2Obj)
|
||||
}
|
||||
})
|
||||
// 1.~~~~TODO:可以从这里修改识别范围~~~~
|
||||
return h2ObjList.filter(
|
||||
(item) =>
|
||||
item.text.includes("CSCI功能需求") ||
|
||||
item.text.includes("CSCI外部接口需求") ||
|
||||
item.text.includes("CSCI能力需求")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 思路:因为mammoth解析后变为html字符串,使用new DOMParser()转换为DOM进行解析
|
||||
* 作用:创建DOMParser()对象,然后解析需求规格说明的html字符串,目前仅支持功能和接口需求
|
||||
* 返回:Array[Object[String,String]]
|
||||
*/
|
||||
export function parseHtmlStringByDemandDut(htmlString) {
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(htmlString, "text/html")
|
||||
const h1h2NodeList = doc.querySelectorAll("h1,h2")
|
||||
// 这一步得到功能需求、接口需求的h2对象,里面有dom、text、chapter
|
||||
const h2ObjArray = getNeedH2NodeList(h1h2NodeList)
|
||||
// 这里开始就要获取全部有用信息:
|
||||
// 2.遍历全部DOM
|
||||
const allArray = Array.from(doc.body.children)
|
||||
const demandArray = []
|
||||
let h2Index = 0
|
||||
let locker = false
|
||||
// 3.将H3和H4的索引增加
|
||||
let currentH3ele = {
|
||||
initChapter: "",
|
||||
index: 0,
|
||||
title: "",
|
||||
ident: "",
|
||||
isIn: false
|
||||
}
|
||||
let currentH4ele = {
|
||||
initChapter: "",
|
||||
index: 0,
|
||||
title: "",
|
||||
ident: ""
|
||||
}
|
||||
let adpterIndex = 0
|
||||
|
||||
allArray.forEach((element) => {
|
||||
// 2.1.找到h2ObjArray的位置
|
||||
if (h2ObjArray[h2Index] && element === h2ObjArray[h2Index].dom) {
|
||||
h2Index += 1
|
||||
currentH3ele.index = 0
|
||||
locker = true
|
||||
} else if (element.tagName === "H1" || element.tagName === "H2") {
|
||||
locker = false
|
||||
} else if (locker && element.tagName !== "H2") {
|
||||
// 就是从H3开始需求的
|
||||
if (element.tagName === "H3") {
|
||||
// 按顺序解析到H3
|
||||
currentH3ele.index += 1
|
||||
currentH4ele.index = 0
|
||||
const splitString = element.innerText.split(/[(())]/)
|
||||
currentH3ele.title = splitString[0]
|
||||
currentH3ele.ident = splitString[1] ? splitString[1] : ""
|
||||
currentH3ele.initChapter = h2ObjArray[h2Index - 1].chapter + "." + currentH3ele.index
|
||||
// 将isIn变为true,说明当前解析在这里面
|
||||
currentH3ele.isIn = true
|
||||
// 段落索引设置0
|
||||
adpterIndex = 0
|
||||
} else if (element.tagName === "H4") {
|
||||
// 按顺序解析到H4
|
||||
currentH4ele.index += 1
|
||||
const splitString = element.innerText.split(/[(())]/)
|
||||
currentH4ele.title = splitString[0]
|
||||
currentH4ele.ident = splitString[1] ? splitString[1] : ""
|
||||
// 将H3的isIn变为false,说明在H4里面不在H3了
|
||||
currentH3ele.isIn = false
|
||||
// chapter
|
||||
currentH4ele.initChapter = currentH3ele.initChapter + "." + currentH4ele.index
|
||||
// 段落索引
|
||||
adpterIndex = 0
|
||||
} else {
|
||||
// 当currentH3ele的title有值的时候开始解析
|
||||
if (currentH3ele.title) {
|
||||
const demandObj = {
|
||||
chapter: "",
|
||||
title: "",
|
||||
ident: "",
|
||||
demandType: "",
|
||||
content: ""
|
||||
}
|
||||
if (currentH3ele.isIn) {
|
||||
demandObj.chapter = currentH3ele.initChapter
|
||||
demandObj.title = currentH3ele.title
|
||||
demandObj.ident = currentH3ele.ident
|
||||
demandObj.demandType = demandObj.title.includes("接口") ? "3" : "1"
|
||||
} else {
|
||||
demandObj.chapter = currentH4ele.initChapter
|
||||
demandObj.title = currentH4ele.title
|
||||
demandObj.ident = currentH4ele.ident
|
||||
demandObj.demandType = demandObj.title.includes("接口") ? "3" : "1"
|
||||
}
|
||||
// 1.解析table元素
|
||||
if (element.tagName === "TABLE") {
|
||||
demandObj.content = element.outerHTML
|
||||
adpterIndex += 1
|
||||
demandObj.ident = demandObj.ident + `-t${adpterIndex}`
|
||||
demandArray.push(demandObj)
|
||||
}
|
||||
// 2.解析p元素-注意排除图片元素
|
||||
if (element.tagName === "P" && !element.querySelector("img")) {
|
||||
demandObj.content = element.innerText
|
||||
adpterIndex += 1
|
||||
demandObj.ident = demandObj.ident + `-p${adpterIndex}`
|
||||
demandArray.push(demandObj)
|
||||
}
|
||||
// 3.解析ol和ul元素
|
||||
if (element.tagName === "OL" || element.tagName === "UL") {
|
||||
demandObj.content = element.innerHTML
|
||||
adpterIndex += 1
|
||||
demandObj.ident = demandObj.ident + `-u${adpterIndex}`
|
||||
demandArray.push(demandObj)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return demandArray
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
// 根据后端定义,不能更改的枚举
|
||||
enum DemandType {
|
||||
gn = "1",
|
||||
xn = "2",
|
||||
jk = "3",
|
||||
kkx = "4",
|
||||
aqx = "5",
|
||||
other = "6"
|
||||
}
|
||||
|
||||
// 数据每一项的结构
|
||||
interface DemandObj {
|
||||
chapter: string
|
||||
title: string
|
||||
ident?: string // 如果其标题有()则放入
|
||||
demandType: DemandType
|
||||
content: string
|
||||
}
|
||||
|
||||
// 定义h元素对象接口
|
||||
interface IHobj {
|
||||
level: number // h1 -> 1
|
||||
title: string // 标题文字
|
||||
ident?: string // 如果有()则放入
|
||||
index: number // 该标题的索引,先map的时候不设置后续再排列
|
||||
demandType: DemandType
|
||||
}
|
||||
|
||||
// h元素对象带计算出的章节号的对象
|
||||
interface IHobjWithChapter extends IHobj {
|
||||
chapter: string
|
||||
}
|
||||
|
||||
export class HtmlParser {
|
||||
domArray: Element[] // 初始化得到一个元素的集合
|
||||
hWithChapter: IHobjWithChapter[]
|
||||
constructor(htmlText: string) {
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(htmlText, "text/html")
|
||||
this.domArray = Array.from(doc.body.children)
|
||||
// 解析domArray将h元素加入
|
||||
const HDomArray = this.domArray.filter((domItem) => domItem.tagName.startsWith("H"))
|
||||
const storeArray: IHobj[] = []
|
||||
HDomArray.forEach((it, i) => {
|
||||
!it.textContent && (it.textContent = "")
|
||||
// 这里判断是什么类型的设计需求 -> 后续可以添加
|
||||
let type: DemandType = DemandType.gn
|
||||
if (it.textContent.includes("接口")) {
|
||||
type = DemandType.jk
|
||||
} else if (it.textContent.includes("性能")) {
|
||||
type = DemandType.xn
|
||||
} else if (it.textContent.includes("可靠")) {
|
||||
type = DemandType.kkx
|
||||
} else if (it.textContent.includes("安全")) {
|
||||
type = DemandType.aqx
|
||||
}
|
||||
// 获取章节号
|
||||
const level = +it.tagName.slice(1)
|
||||
// 1.判断当前的章节级别的前一个级别的level是否一样/小于/大于
|
||||
let index = 0
|
||||
if (i > 0) {
|
||||
// 找上一个标题对象的level
|
||||
if (level === storeArray[i - 1].level) {
|
||||
index = storeArray[i - 1].index + 1
|
||||
} else if (level < storeArray[i - 1].level) {
|
||||
// 如果等级小于上一个标题对象,找storeArray里面level一样的长度
|
||||
for (let j = storeArray.length - 1; j >= 0; j--) {
|
||||
if (storeArray[j].level === level) {
|
||||
index = storeArray[j].index + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
storeArray.push({
|
||||
level: level,
|
||||
title: it.textContent,
|
||||
ident: it.textContent.match(/[\((](.*?)[\))]/)?.[1],
|
||||
index: index,
|
||||
demandType: type
|
||||
})
|
||||
})
|
||||
// 直接将storeArray计算出章节号
|
||||
const chapterArray: any[] = storeArray.map((it, indx) => {
|
||||
let str = it.index + 1 + ""
|
||||
for (let i = it.level - 1; i > 0; i--) {
|
||||
for (let j = indx - 1; j >= 0; j--) {
|
||||
if (storeArray[j].level === i) {
|
||||
str = storeArray[j].index + 1 + "." + str
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
...it,
|
||||
chapter: str
|
||||
}
|
||||
})
|
||||
this.hWithChapter = chapterArray
|
||||
}
|
||||
/**
|
||||
* 将所有Element元素遍历,输出列表格式
|
||||
*/
|
||||
parseToArray(): DemandObj[] {
|
||||
const resArr: DemandObj[] = []
|
||||
let index = 0
|
||||
let currentHElement: IHobjWithChapter = this.hWithChapter[index]
|
||||
let adapterIndex: number = 1
|
||||
this.domArray.forEach((item) => {
|
||||
// 1.如果循环到H元素,将其存入
|
||||
if (item.tagName.startsWith("H")) {
|
||||
const text = item.textContent
|
||||
if (text !== currentHElement.title) {
|
||||
currentHElement = this.hWithChapter[index + 1]
|
||||
index++
|
||||
adapterIndex = 1
|
||||
}
|
||||
} else {
|
||||
// 2.构造每一项放入 - chapter和content计算
|
||||
let content = ""
|
||||
let ident = currentHElement.ident
|
||||
if (!ident) {
|
||||
ident = ""
|
||||
}
|
||||
// 这里对图片进行处理
|
||||
if (item.querySelector("img")) {
|
||||
const img = item.querySelector("img")
|
||||
if (img) {
|
||||
const strblob = img.src + ""
|
||||
const blob = strblob.split(",")[1]
|
||||
content = `<img src="data:image/png;base64,${blob}" />`
|
||||
}
|
||||
ident += `-g${adapterIndex}`
|
||||
adapterIndex++
|
||||
resArr.push({
|
||||
chapter: currentHElement.chapter,
|
||||
title: currentHElement.title,
|
||||
ident: ident,
|
||||
demandType: currentHElement.demandType,
|
||||
content
|
||||
})
|
||||
} else {
|
||||
// 如果不是图片进行判断
|
||||
if (item.textContent) {
|
||||
if (item.tagName === "TABLE") {
|
||||
content = item.outerHTML
|
||||
ident += `-t${adapterIndex}`
|
||||
adapterIndex++
|
||||
}
|
||||
if (item.tagName === "P" && !item.querySelector("img")) {
|
||||
content = item.innerHTML.trim()
|
||||
ident += `-p${adapterIndex}`
|
||||
adapterIndex++
|
||||
}
|
||||
if (item.tagName === "OL" || item.tagName === "UL") {
|
||||
content = item.innerHTML.trim()
|
||||
ident += `-u${adapterIndex}`
|
||||
adapterIndex++
|
||||
}
|
||||
// title要将括号去除
|
||||
resArr.push({
|
||||
chapter: currentHElement.chapter,
|
||||
title: currentHElement.title,
|
||||
ident: ident,
|
||||
demandType: currentHElement.demandType,
|
||||
content
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return resArr
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ const beiceType: BeiceTypeT[] = [
|
||||
{ label: "设计说明", value: "SJ" },
|
||||
{ label: "需求文档", value: "XQ" },
|
||||
{ label: "通信协议", value: "XY" },
|
||||
{ label: "研制总要求", value: "YZ" }
|
||||
{ label: "研制总要求/技术协议等", value: "YZ" }
|
||||
]
|
||||
|
||||
export default beiceType
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ref } from "vue"
|
||||
import { ref, shallowRef } from "vue"
|
||||
import { useRoute } from "vue-router"
|
||||
import beiceType from "@/views/project/round/beiceType"
|
||||
// 导入自定义组件
|
||||
import UploadInput from "@/components/UploadInput/index.vue"
|
||||
|
||||
export default function (crudOrFormRef: any) {
|
||||
// global
|
||||
@@ -8,10 +10,8 @@ export default function (crudOrFormRef: any) {
|
||||
// 计算注释率计算crud/form的数据,判断
|
||||
const calcPercent = () => {
|
||||
const formData = crudOrFormRef.value.getFormData()
|
||||
const { code_line, comment_line, mix_line, black_line } = formData
|
||||
const total_line = +black_line + +code_line + +comment_line + +mix_line
|
||||
const comment_total = +comment_line + +mix_line
|
||||
formData.comment_percent = `${(comment_total / total_line).toFixed(2).toString()}%`
|
||||
const { total_lines, comment_lines } = formData
|
||||
formData.comment_percent = `${(comment_lines / total_lines).toFixed(2).toString()}%`
|
||||
}
|
||||
const crudColumns = ref([
|
||||
{
|
||||
@@ -51,28 +51,24 @@ export default function (crudOrFormRef: any) {
|
||||
translation: true,
|
||||
tagColors: { XQ: "blue", SO: "green", SJ: "orangered", XY: "pinkpurple", YZ: "red" }
|
||||
},
|
||||
onControl: (value) => {
|
||||
onControl: (value: string) => {
|
||||
if (value === "SO") {
|
||||
return {
|
||||
black_line: { display: true },
|
||||
code_line: { display: true },
|
||||
mix_line: { display: true },
|
||||
comment_line: { display: true },
|
||||
total_code_line: { display: true },
|
||||
total_line: { display: true },
|
||||
total_lines: { display: true },
|
||||
effective_lines: { display: true },
|
||||
comment_lines: { display: true },
|
||||
comment_percent: { display: true },
|
||||
upload: { display: true },
|
||||
release_date: { display: false }
|
||||
}
|
||||
} else {
|
||||
// 其他数据清除
|
||||
return {
|
||||
black_line: { display: false },
|
||||
code_line: { display: false },
|
||||
mix_line: { display: false },
|
||||
comment_line: { display: false },
|
||||
total_code_line: { display: false },
|
||||
total_line: { display: false },
|
||||
total_lines: { display: false },
|
||||
effective_lines: { display: false },
|
||||
comment_lines: { display: false },
|
||||
comment_percent: { display: false },
|
||||
upload: { display: false },
|
||||
release_date: { display: true }
|
||||
}
|
||||
}
|
||||
@@ -123,49 +119,36 @@ export default function (crudOrFormRef: any) {
|
||||
formType: "date"
|
||||
},
|
||||
{
|
||||
title: "空行",
|
||||
title: "总行数",
|
||||
hide: true,
|
||||
align: "center",
|
||||
dataIndex: "black_line",
|
||||
dataIndex: "total_lines",
|
||||
formType: "input-number",
|
||||
commonRules: [{ required: true, message: "空行数必填" }],
|
||||
commonRules: [{ required: true, message: "总行数必填" }],
|
||||
min: 0,
|
||||
onControl: () => {
|
||||
calcPercent()
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "纯代码行",
|
||||
title: "有效行数",
|
||||
hide: true,
|
||||
align: "center",
|
||||
dataIndex: "code_line",
|
||||
dataIndex: "effective_lines",
|
||||
formType: "input-number",
|
||||
commonRules: [{ required: true, message: "纯代码行数必填" }],
|
||||
commonRules: [{ required: true, message: "有效行数必填" }],
|
||||
min: 0,
|
||||
onControl: () => {
|
||||
calcPercent()
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "纯注释行",
|
||||
title: "注释行数",
|
||||
hide: true,
|
||||
align: "center",
|
||||
dataIndex: "comment_line",
|
||||
dataIndex: "comment_lines",
|
||||
formType: "input-number",
|
||||
commonRules: [{ required: true, message: "纯注释行数必填" }],
|
||||
min: 0,
|
||||
onControl: () => {
|
||||
calcPercent()
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "混合行",
|
||||
hide: true,
|
||||
align: "center",
|
||||
dataIndex: "mix_line",
|
||||
formType: "input-number",
|
||||
help: "混合行是指:代码中一行即包含代码也包含注释",
|
||||
commonRules: [{ required: true, message: "混合行数必填" }],
|
||||
commonRules: [{ required: true, message: "注释行数必填" }],
|
||||
min: 0,
|
||||
onControl: () => {
|
||||
calcPercent()
|
||||
@@ -180,6 +163,15 @@ export default function (crudOrFormRef: any) {
|
||||
addDisabled: true,
|
||||
editDisabled: true,
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
title: "上传源代码",
|
||||
align: "center",
|
||||
dataIndex: "upload",
|
||||
placeholder: "上传源代码",
|
||||
hide: true,
|
||||
formType: "component",
|
||||
component: shallowRef(UploadInput)
|
||||
}
|
||||
])
|
||||
return crudColumns
|
||||
|
||||
@@ -10,7 +10,78 @@ export default function useOptions(formRef: any) {
|
||||
})
|
||||
const crudColumns = useColumn(formRef)
|
||||
const columnOptions = computed(() => {
|
||||
return tool.renameKeyInArray(crudColumns.value, "commonRules", "rules")
|
||||
// 处理表单布局
|
||||
const transformColumns = tool.renameKeyInArray(crudColumns.value, "commonRules", "rules")
|
||||
// 取出字段column对象
|
||||
const identColumn = transformColumns.find((item: any) => item.dataIndex === "ident")
|
||||
const nameColumn = transformColumns.find((item: any) => item.dataIndex === "name")
|
||||
const designPersonColumn = transformColumns.find((item: any) => item.dataIndex === "designPerson")
|
||||
const testPersonColumn = transformColumns.find((item: any) => item.dataIndex === "testPerson")
|
||||
const monitorPersonColumn = transformColumns.find((item: any) => item.dataIndex === "monitorPerson")
|
||||
const summarizeColumn = transformColumns.find((item: any) => item.dataIndex === "summarize")
|
||||
const initializationColumn = transformColumns.find((item: any) => item.dataIndex === "initialization")
|
||||
const premiseColumn = transformColumns.find((item: any) => item.dataIndex === "premise")
|
||||
const exe_timeColumn = transformColumns.find((item: any) => item.dataIndex === "exe_time")
|
||||
// 组装表单布局
|
||||
const identAndNameColumn = {
|
||||
formType: "grid",
|
||||
cols: [
|
||||
{ span: 12, formList: [identColumn] },
|
||||
{ span: 12, formList: [nameColumn] }
|
||||
]
|
||||
}
|
||||
const cardColumn = {
|
||||
formType: "card",
|
||||
customClass: ["ml-5", "mb-3", "py-0", "px-0"],
|
||||
title: "人员信息",
|
||||
formList: [
|
||||
{
|
||||
formType: "grid",
|
||||
cols: [
|
||||
{ span: 8, formList: [designPersonColumn] },
|
||||
{ span: 8, formList: [testPersonColumn] },
|
||||
{ span: 8, formList: [monitorPersonColumn] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
const summarizeColumnNew = {
|
||||
formType: "grid",
|
||||
cols: [{ span: 24, formList: [summarizeColumn] }]
|
||||
}
|
||||
const initializationColumnNew = {
|
||||
formType: "grid",
|
||||
cols: [{ span: 24, formList: [initializationColumn] }]
|
||||
}
|
||||
const premiseAndExeColumn = {
|
||||
formType: "grid",
|
||||
cols: [
|
||||
{ span: 12, formList: [premiseColumn] },
|
||||
{ span: 12, formList: [exe_timeColumn] }
|
||||
]
|
||||
}
|
||||
// 取除原数组里面的内容
|
||||
const newColumnsArray = transformColumns.filter(
|
||||
(it: any) =>
|
||||
it.dataIndex !== "ident" &&
|
||||
it.dataIndex !== "name" &&
|
||||
it.dataIndex !== "designPerson" &&
|
||||
it.dataIndex !== "testPerson" &&
|
||||
it.dataIndex !== "monitorPerson" &&
|
||||
it.dataIndex !== "summarize" &&
|
||||
it.dataIndex !== "initialization" &&
|
||||
it.dataIndex !== "premise" &&
|
||||
it.dataIndex !== "exe_time"
|
||||
)
|
||||
newColumnsArray.unshift(
|
||||
identAndNameColumn,
|
||||
cardColumn,
|
||||
summarizeColumnNew,
|
||||
initializationColumnNew,
|
||||
premiseAndExeColumn
|
||||
)
|
||||
return newColumnsArray
|
||||
})
|
||||
|
||||
return { options, columnOptions }
|
||||
}
|
||||
|
||||
@@ -140,9 +140,20 @@ export default function (crudOrFormRef: any, problemFormRef?: any) {
|
||||
},
|
||||
{
|
||||
title: "执行时间",
|
||||
align: "center",
|
||||
dataIndex: "exe_time",
|
||||
formType: "date",
|
||||
customRender: ({ record }) => {
|
||||
// 如果不存在exe_time则显示为“没有设置”
|
||||
return record.exe_time ? record.exe_time : <a-tag color="red">未填写</a-tag>
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "时序图(cpu不填写此字段)",
|
||||
hide: true,
|
||||
formType: "date"
|
||||
dataIndex: "timing_diagram",
|
||||
addDefaultValue: "",
|
||||
formType: "editor"
|
||||
},
|
||||
{
|
||||
title: "测试步骤",
|
||||
|
||||
@@ -123,7 +123,7 @@ export default function (crudRef: Ref<InstanceType<typeof MaCrud>>) {
|
||||
},
|
||||
{
|
||||
formType: "card",
|
||||
customClass: ["ml-10", "mb-3", "py-0", "px-0"],
|
||||
customClass: ["ml-5", "mb-3", "py-0", "px-0"],
|
||||
title: "人员信息",
|
||||
formList: [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user