This commit is contained in:
2024-05-30 11:29:44 +08:00
parent 91864994af
commit 7b37fdd2bb
19 changed files with 1396 additions and 268 deletions

View File

@@ -8,6 +8,7 @@
:mask-closable="false"
ok-text="录入"
:esc-to-close="false"
:on-before-ok="handleModalSubmit"
>
<div class="uploadContainer">
<span :style="{ marginBottom: '10px', flex: '0 1 160px' }">上传设计需求.docx:</span>
@@ -16,6 +17,7 @@
:custom-request="handleRquest"
:limit="1"
accept=".docx"
@change="handleUploadChange"
></a-upload>
</div>
<a-alert :style="{ margin: '10px 0' }" type="warning"
@@ -26,89 +28,106 @@
>2</span
>; 2.外部接口章节必须包含<span class="important-text">CSCI外部接口需求</span
>文字,且章节级别必须为<span class="important-text">2</span
>暂时只能解析这两个章节如果错误请修改文档文字后再次上传后续优化</a-alert
>暂时只能解析这两个章节如果章节号有标识则使用<span class="important-text"
>RQGN01-UART-ZRZL</span
><span class="important-text">(RQGN01-UART-XBRCV)</span>包含在里面</a-alert
>
<div class="operation-container">
<span :style="{ marginRight: '10px', fontWeight: 700 }">操作按钮:</span>
<a-button type="primary" @click="handleCreateAtLatest">新增一条</a-button>
<a-space>
<a-button type="primary" @click="handleCreateAtLatest">新增一条</a-button>
<a-button type="outline" status="warning" @click="handleResetData">重置数据</a-button>
</a-space>
</div>
<div class="demand-container">
<a-collapse show-expand-icon>
<a-collapse-item v-for="(item, index) in htmlData" :key="index">
<template #header>
<a-input-group>
<a-input
placeholder="章节号"
v-model="item.capter"
:style="{ width: '110px' }"
@click.stop.prevent
></a-input>
<a-input
placeholder="标题"
v-model="item.title"
:style="{ width: '350px' }"
@click.stop.prevent
></a-input>
<a-input
placeholder="标识"
v-model="item.ident"
:style="{ width: '160px' }"
@click.stop.prevent
></a-input>
<a-select
:style="{ width: '200px' }"
placeholder="请选择设计需求类型"
@click.stop.prevent
v-model="item.demandType"
>
<a-option v-for="it in demandType" :value="it.key">{{ it.title }}</a-option>
</a-select>
</a-input-group>
<a-spin :loading="loading" tip="解析word完成需要时间渲染HTML元素..." :style="{ width: '100%' }">
<div class="demand-container">
<a-list :data="htmlData" :pagination-props="{ defaultPageSize: 15, total: htmlData.length }">
<template #item="{ item, index }">
<a-list-item>
<div class="item-container">
<a-input-group>
<a-input
placeholder="章节号"
v-model="item.chapter"
:style="{ width: '100px' }"
@click.stop.prevent
></a-input>
<a-input
placeholder="标题"
v-model="item.title"
:style="{ width: '300px' }"
@click.stop.prevent
></a-input>
<a-input
placeholder="标识"
v-model="item.ident"
:style="{ width: '200px' }"
@click.stop.prevent
></a-input>
<a-select
:style="{ width: '150px' }"
placeholder="请选择设计需求类型"
@click.stop.prevent
v-model="item.demandType"
>
<a-option v-for="it in demandType" :value="it.key">{{ it.title }}</a-option>
</a-select>
</a-input-group>
<a-button-group>
<a-button
type="primary"
status="success"
size="small"
@click.stop.prevent="handledownCreate(index)"
>
<template #icon>
<icon-plus />
</template>
下方新增
</a-button>
<a-button
type="primary"
status="danger"
size="small"
@click.stop.prevent="handleDelete(index)"
>
<template #icon>
<icon-delete />
</template>
删除该条
</a-button>
</a-button-group>
</div>
<div class="content">
<MaEditor v-model="item.content" />
</div>
</a-list-item>
</template>
<div class="content">
<a-textarea
show-word-limit
placeholder="设计需求内容"
auto-size
v-model="item.content"
allow-clear
/>
</div>
<template #extra>
<a-button-group>
<a-button
type="primary"
status="success"
size="mini"
@click.stop.prevent="handledownCreate(index)"
>
+下方新增
</a-button>
<a-button
type="primary"
status="danger"
size="mini"
@click.stop.prevent="handleDelete(index)"
>
删除该条
</a-button>
</a-button-group>
</template>
</a-collapse-item>
</a-collapse>
</div>
</a-list>
</div>
</a-spin>
</a-modal>
</div>
</template>
<script setup>
import { ref } from "vue"
import { ref, nextTick, watch } from "vue"
import mammoth from "mammoth"
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 { 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 = {
capter: "",
chapter: "",
title: "",
ident: "",
demandType: "",
@@ -124,34 +143,21 @@ getDictDemandType()
// 弹窗显示
const modalVisible = ref(false)
// 全部html数据-循环创建折叠项
const htmlData = ref([
{
capter: "4.2.1.1",
title: "解析并执行注入指令",
ident: "RQGN01-UART-ZRZL",
demandType: "1",
content: `WXT星载触发处理单元主控软件通过UART总线与PDPU之间通讯完成数据注入和广播注入的接收其中包括注入指令、星表、主控软件目标文件、FPGA目标文件、算法参数、广播时间码、姿态等数据接收。
通讯过程按照《EP卫星PDPU与WXT星载触发处理单元的通信协议》有关规定执行。`
},
{
capter: "4.2.1.2",
title: "接受星表注入",
ident: "RQGN01-UART",
demandType: "1",
content: `星表数据存储在触发处理单元NORFLASH基地址为0xBC000000偏移地址0x100000~1FFFFF空间中主备份NORFLASH存储同一份星表数据。软件在初始化的时候将NORFLASH中的星表数据读取到算法调用内存空间。`
},
{
capter: "4.2.1.3",
title: "接收软件代码重构",
ident: "RQGN01-1553B-SWRCV",
demandType: "1",
content: `主控软件接收软件重构目标文件支持乱序接收软件重构目标代码每接收到一个首包包序号为0则认为是新一次目标文件接收开始更新“软件重构状态”为软件重构接收中若没有收到首包对于之后接收到的中间包和尾包都丢弃当尾包包序号以前的所有序号的数据包均接收完毕则认为主控软件目标文件接收完毕更新“软件重构状态”为软件重构写入中在接收过程中如果接收到中间包的包序号大于尾包包序号则丢弃该中间包并置“软件重构状态为”包序号错之后停止当前目标文件接收直至接收到新的首包如果前后收到两个包序号相同的数据包则保留后接收到的数据包尾包以第一次收到的为准`
}
])
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)
@@ -165,6 +171,11 @@ const handleRquest = function (options) {
const rawHtml = res.value
const finalData = parseHtmlStringByDemandDut(rawHtml)
// 将finalData赋值给响应式数据ref变量htmlData
htmlData.value = finalData
// ~~~~使用nextTick等待DOM更新完成~~~~
nextTick(() => {
loading.value = false
})
})
.catch(function (error) {
console.log("处理错误导致失败,请检查前端代码")
@@ -178,6 +189,10 @@ 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))
@@ -192,6 +207,33 @@ const open = function () {
// 打开时候传入对象可初始化from
modalVisible.value = true
}
// 为了性能监听modal关闭则清空数据
watch(
() => modalVisible.value,
(newValue) => {
if (!newValue) {
htmlData.value = []
}
}
)
// 提交事件
const handleModalSubmit = async () => {
if (!htmlData.value.length) {
Message.error("请添加设计需求后再提交...")
return false
}
const res = await demandApi.multiSave({
projectId: route.query.id,
key: route.query.key,
data: htmlData.value
})
if (res.code === 200) {
treeDataStore.updateDesignDemandTreeData(res.data, route.query.id)
Message.success("批量新增设计需求成功...")
return true
}
return false
}
// 暴露该组件ref的方法
defineExpose({ open })
</script>
@@ -207,8 +249,9 @@ defineExpose({ open })
}
.demand-container {
width: 100%;
height: 100%;
height: 55vh;
padding: 5px;
overflow: auto;
}
.operation-container {
margin-bottom: 5px;
@@ -217,4 +260,12 @@ defineExpose({ open })
font-weight: 700;
color: red;
}
.item-container {
width: 100%;
display: flex;
justify-content: space-between;
padding: 5px 5px;
border: 1px solid #ccc;
border-bottom: none;
}
</style>

View File

@@ -0,0 +1,258 @@
<template>
<div class="file-input-container">
<a-modal
v-model:visible="modalVisible"
width="80%"
title="设计需求批量写入"
:unmount-on-close="true"
:mask-closable="false"
ok-text="录入"
:esc-to-close="false"
:on-before-ok="handleModalSubmit"
>
<div class="uploadContainer">
<span :style="{ marginBottom: '10px', flex: '0 1 160px' }">上传设计需求.docx:</span>
<a-upload
:style="{ marginBottom: '10px' }"
:custom-request="handleRquest"
:limit="1"
accept=".docx"
@change="handleUploadChange"
></a-upload>
</div>
<a-alert :style="{ margin: '10px 0' }" type="warning"
>上传录入提示信息硬性要求本辅助程序根据GJB438C文档结构进行解析要求1.功能需求章节必须包含<span
class="important-text"
>CSCI功能需求</span
><span class="important-text">CSCI能力需求</span>文字,且章节级别必须为<span class="important-text"
>2</span
>; 2.外部接口章节必须包含<span class="important-text">CSCI外部接口需求</span
>文字,且章节级别必须为<span class="important-text">2</span
>暂时只能解析这两个章节如果章节号有标识则使用<span class="important-text"
>RQGN01-UART-ZRZL</span
><span class="important-text">(RQGN01-UART-XBRCV)</span>包含在里面</a-alert
>
<div class="operation-container">
<span :style="{ marginRight: '10px', fontWeight: 700 }">操作按钮:</span>
<a-space>
<a-button type="primary" @click="handleCreateAtLatest">新增一条</a-button>
<a-button type="outline" status="warning" @click="handleResetData">重置数据</a-button>
</a-space>
</div>
<a-spin :loading="loading" tip="解析word完成需要时间渲染HTML元素..." :style="{ width: '100%' }">
<div class="demand-container">
<Empty v-if="!htmlData.length"></Empty>
<a-collapse show-expand-icon destroy-on-hide v-show="htmlData.length">
<a-collapse-item v-for="(item, index) in htmlData" :key="index">
<template #header>
<a-input-group>
<a-input
placeholder="章节号"
v-model="item.chapter"
:style="{ width: '110px' }"
@click.stop.prevent
></a-input>
<a-input
placeholder="标题"
v-model="item.title"
:style="{ width: '300px' }"
@click.stop.prevent
></a-input>
<a-input
placeholder="标识"
v-model="item.ident"
:style="{ width: '200px' }"
@click.stop.prevent
></a-input>
<a-select
:style="{ width: '200px' }"
placeholder="请选择设计需求类型"
@click.stop.prevent
v-model="item.demandType"
>
<a-option v-for="it in demandType" :value="it.key">{{ it.title }}</a-option>
</a-select>
</a-input-group>
</template>
<div class="content">
<MaEditor v-model="item.content" />
</div>
<template #extra>
<a-button-group>
<a-button
type="primary"
status="success"
size="mini"
@click.stop.prevent="handledownCreate(index)"
>
+下方新增
</a-button>
<a-button
type="primary"
status="danger"
size="mini"
@click.stop.prevent="handleDelete(index)"
>
删除该条
</a-button>
</a-button-group>
</template>
</a-collapse-item>
</a-collapse>
</div>
</a-spin>
</a-modal>
</div>
</template>
<script setup>
import { ref, nextTick, watch } from "vue"
import mammoth from "mammoth"
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 { 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选项
let demandType = []
const getDictDemandType = async function () {
const res = await dictApi.getDictByCode({ code: "demandType" })
demandType = res.data
}
getDictDemandType()
// 弹窗显示
const modalVisible = ref(false)
// 全部html数据-循环创建折叠项
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 finalData = parseHtmlStringByDemandDut(rawHtml)
// 将finalData赋值给响应式数据ref变量htmlData
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)
}
// 点击单条右侧按钮:删除
const handleDelete = (index) => {
htmlData.value.splice(index, 1)
}
// 打开弹窗并初始化form数据
const open = function () {
// 打开时候传入对象可初始化from
modalVisible.value = true
}
// 为了性能监听modal关闭则清空数据
watch(
() => modalVisible.value,
(newValue) => {
if (!newValue) {
htmlData.value = []
}
}
)
// 提交事件
const handleModalSubmit = async () => {
if (!htmlData.value.length) {
Message.error("请添加设计需求后再提交...")
return false
}
const res = await demandApi.multiSave({
projectId: route.query.id,
key: route.query.key,
data: htmlData.value
})
if (res.code === 200) {
treeDataStore.updateDesignDemandTreeData(res.data, route.query.id)
Message.success("批量新增设计需求成功...")
return true
}
return false
}
// 暴露该组件ref的方法
defineExpose({ open })
</script>
<style lang="less" scoped>
.uploadContainer {
display: flex;
align-items: center;
& span {
font-size: 1em;
font-weight: 700;
}
}
.demand-container {
width: 100%;
height: 55vh;
padding: 5px;
overflow: auto;
}
.operation-container {
margin-bottom: 5px;
}
.important-text {
font-weight: 700;
color: red;
}
</style>

View File

@@ -22,7 +22,7 @@ function getNeedH2NodeList(h1h2Node) {
h2ObjList.push(h2Obj)
}
})
// 1.后续增加识别的章节可从这里修改
// 1.~~~~TODO:可以从这里修改识别范围~~~~
return h2ObjList.filter(
(item) =>
item.text.includes("CSCI功能需求") ||
@@ -48,16 +48,101 @@ export function parseHtmlStringByDemandDut(htmlString) {
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") {
console.log(element)
// 这里就是有效信息了注意还要找H3/H4等信息 - 然后通过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
}