0.0.1版本

This commit is contained in:
2024-07-22 18:57:12 +08:00
parent e071bee532
commit bf0b0f6080
49 changed files with 868 additions and 499 deletions

View File

@@ -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(() => {

View File

@@ -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>

View File

@@ -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>

View 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
}
}