0.0.1版本
This commit is contained in:
@@ -20,18 +20,12 @@
|
||||
@change="handleUploadChange"
|
||||
></a-upload>
|
||||
</div>
|
||||
<a-alert :style="{ margin: '10px 0' }" type="warning"
|
||||
>上传录入提示信息:硬性要求,本辅助程序根据GJB438C文档结构进行解析,要求:1.功能需求章节必须包含<span
|
||||
<a-alert :style="{ margin: '10px 0' }" type="warning">
|
||||
请去除需求规格说明文件中不必要的部分再来此处解析,注意:本系统不支持word中emf格式图片,如果使用了visio等图片请<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
|
||||
>
|
||||
>在word变为普通图片上传</span
|
||||
>
|
||||
</a-alert>
|
||||
<div class="operation-container">
|
||||
<span :style="{ marginRight: '10px', fontWeight: 700 }">操作按钮:</span>
|
||||
<a-space>
|
||||
@@ -39,7 +33,7 @@
|
||||
<a-button type="outline" status="warning" @click="handleResetData">重置数据</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-spin :loading="loading" tip="解析word完成,需要时间渲染HTML元素..." :style="{ width: '100%' }">
|
||||
<a-spin :loading="loading" tip="解析word完成,正在渲染界面..." :style="{ width: '100%' }">
|
||||
<div class="demand-container">
|
||||
<a-list :data="htmlData" :pagination-props="{ defaultPageSize: 15, total: htmlData.length }">
|
||||
<template #item="{ item, index }">
|
||||
@@ -117,6 +111,7 @@ 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 { useTreeDataStore } from "@/store"
|
||||
// 其他初始化数据
|
||||
const route = useRoute()
|
||||
@@ -169,8 +164,8 @@ const handleRquest = function (options) {
|
||||
// 已经上传到浏览器了,需要解析为列表
|
||||
onSuccess(1)
|
||||
const rawHtml = res.value
|
||||
const finalData = parseHtmlStringByDemandDut(rawHtml)
|
||||
// 将finalData赋值给响应式数据ref变量:htmlData
|
||||
const parser = new HtmlParser(rawHtml)
|
||||
const finalData = parser.parseToArray()
|
||||
htmlData.value = finalData
|
||||
// ~~~~使用nextTick:等待DOM更新完成~~~~
|
||||
nextTick(() => {
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
<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>
|
||||
@@ -14,6 +14,9 @@
|
||||
上传需求规格说明快捷录入
|
||||
</a-button>
|
||||
</template>
|
||||
<!-- 字段的前缀后缀的插槽 -->
|
||||
<!-- 版本字段的插槽 -->
|
||||
<template #inputPrepend-ident> SJ-XX- </template>
|
||||
</ma-crud>
|
||||
</div>
|
||||
<file-input-modal ref="fileInputRef"></file-input-modal>
|
||||
@@ -119,6 +122,7 @@ const crudColumns = ref([
|
||||
title: "ID",
|
||||
align: "center",
|
||||
width: 50,
|
||||
hide: true,
|
||||
dataIndex: "id",
|
||||
commonRules: [{ required: true, message: "ID必填" }],
|
||||
validateTrigger: "blur"
|
||||
@@ -132,7 +136,8 @@ const crudColumns = ref([
|
||||
search: true,
|
||||
validateTrigger: "blur",
|
||||
placeholder: "请输入文档中设计需求的标识",
|
||||
help: '若不知道则填"无"或不填'
|
||||
help: '若不知道则填"无"或不填',
|
||||
openPrepend: true
|
||||
},
|
||||
{
|
||||
title: "设需名称",
|
||||
@@ -191,8 +196,7 @@ const crudColumns = ref([
|
||||
{
|
||||
title: "接口来源",
|
||||
dataIndex: "source",
|
||||
hide: true,
|
||||
extra: "接口独有四个字段,决定大纲的接口列表信息"
|
||||
hide: true
|
||||
},
|
||||
{
|
||||
title: "目的地",
|
||||
@@ -206,11 +210,10 @@ const crudColumns = ref([
|
||||
{
|
||||
title: "接口类型",
|
||||
dataIndex: "type",
|
||||
hide: true,
|
||||
extra: "接口类型例如:网络"
|
||||
hide: true
|
||||
},
|
||||
{
|
||||
title: "接口协议",
|
||||
title: "接口内容",
|
||||
dataIndex: "protocal",
|
||||
hide: true
|
||||
}
|
||||
@@ -232,6 +235,10 @@ const fileInputRef = ref(null)
|
||||
const handleAddFileInputDemand = () => {
|
||||
fileInputRef.value.open()
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: "dut"
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
174
cdTMP/src/views/project/dut/tools/parser.ts
Normal file
174
cdTMP/src/views/project/dut/tools/parser.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
// 根据后端定义,不能更改的枚举
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user