123321
This commit is contained in:
@@ -34,6 +34,17 @@ export default {
|
||||
data: params
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 批量添加设计需求
|
||||
* @returns
|
||||
*/
|
||||
multiSave(params = {}) {
|
||||
return request({
|
||||
url: "/project/designDemand/multi_save",
|
||||
method: "post",
|
||||
data: params
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 更新dut
|
||||
* @returns
|
||||
@@ -66,5 +77,5 @@ export default {
|
||||
method: "get",
|
||||
params
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
26
cdTMP/src/components/Empty/index.vue
Normal file
26
cdTMP/src/components/Empty/index.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div class="empty-container">
|
||||
<icon-empty :size="60" />
|
||||
<p>{{ text }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue"
|
||||
const text = ref("暂无数据")
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.empty-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
color: #aaa;
|
||||
p {
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -22,6 +22,8 @@ import MaIcon from "./ma-icon/index.vue"
|
||||
import MaCodeEditor from "./ma-codeEditor/index.vue"
|
||||
import MaUserInfo from "./ma-userInfo/index.vue"
|
||||
import MaCityLinkage from "./ma-cityLinkage/index.vue"
|
||||
// 后续增加的全局组件
|
||||
import Empty from "./Empty/index.vue"
|
||||
|
||||
use([
|
||||
CanvasRenderer,
|
||||
@@ -52,5 +54,7 @@ export default {
|
||||
Vue.component("MaCodeEditor", MaCodeEditor)
|
||||
Vue.component("MaUserInfo", MaUserInfo)
|
||||
Vue.component("MaCityLinkage", MaCityLinkage)
|
||||
// 后续增加的组件
|
||||
Vue.component("Empty", Empty)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -840,5 +840,6 @@ defineExpose({
|
||||
}
|
||||
._crud-footer {
|
||||
z-index: 10;
|
||||
height: 80px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,28 +15,28 @@ import "tinymce/icons/default"
|
||||
import "tinymce/models/dom"
|
||||
import "tinymce/themes/silver"
|
||||
|
||||
import "tinymce/plugins/advlist" //高级列表
|
||||
import "tinymce/plugins/anchor" //锚点
|
||||
import "tinymce/plugins/autolink" //自动链接
|
||||
import "tinymce/plugins/autosave" //自动存稿
|
||||
import "tinymce/plugins/charmap" //特殊字符
|
||||
import "tinymce/plugins/code" //编辑源码
|
||||
import "tinymce/plugins/codesample" //代码示例
|
||||
import "tinymce/plugins/directionality" //文字方向
|
||||
import "tinymce/plugins/image" //插入编辑图片
|
||||
import "tinymce/plugins/importcss" //引入css
|
||||
import "tinymce/plugins/insertdatetime" //插入日期时间
|
||||
import "tinymce/plugins/lists" //列表插件
|
||||
import "tinymce/plugins/nonbreaking" //插入不间断空格
|
||||
import "tinymce/plugins/pagebreak" //插入分页符
|
||||
import "tinymce/plugins/preview" //预览
|
||||
import "tinymce/plugins/quickbars" //快速工具栏
|
||||
import "tinymce/plugins/save" //保存
|
||||
import "tinymce/plugins/searchreplace" //查找替换
|
||||
import "tinymce/plugins/table" //表格
|
||||
import "tinymce/plugins/visualblocks" //显示元素范围
|
||||
import "tinymce/plugins/visualchars" //显示不可见字符
|
||||
import "tinymce/plugins/wordcount" //字数统计
|
||||
// import "tinymce/plugins/advlist" // 高级列表
|
||||
// import "tinymce/plugins/anchor" // 锚点
|
||||
// import "tinymce/plugins/autolink" // 自动链接
|
||||
import "tinymce/plugins/autosave" // 自动存稿
|
||||
// import "tinymce/plugins/charmap" // 特殊字符
|
||||
import "tinymce/plugins/code" // 编辑源码
|
||||
// import "tinymce/plugins/codesample" // 代码示例
|
||||
// import "tinymce/plugins/directionality" // 文字方向
|
||||
import "tinymce/plugins/image" // 插入编辑图片
|
||||
import "tinymce/plugins/importcss" // 引入css
|
||||
// import "tinymce/plugins/insertdatetime" // 插入日期时间
|
||||
import "tinymce/plugins/lists" // 列表插件
|
||||
import "tinymce/plugins/nonbreaking" // 插入不间断空格
|
||||
// import "tinymce/plugins/pagebreak" // 插入分页符
|
||||
// import "tinymce/plugins/preview" // 预览
|
||||
import "tinymce/plugins/quickbars" // 快速工具栏
|
||||
import "tinymce/plugins/save" // 保存
|
||||
import "tinymce/plugins/searchreplace" // 查找替换
|
||||
import "tinymce/plugins/table" // 表格
|
||||
// import "tinymce/plugins/visualblocks" //显示元素范围
|
||||
import "tinymce/plugins/visualchars" // 显示不可见字符
|
||||
// import "tinymce/plugins/wordcount" // 字数统计
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
@@ -46,15 +46,14 @@ const props = defineProps({
|
||||
id: { type: String, default: () => "tinymce" + new Date().getTime().toString() },
|
||||
plugins: {
|
||||
type: [String, Array],
|
||||
default:
|
||||
"preview searchreplace autolink directionality visualblocks visualchars code codesample table charmap nonbreaking insertdatetime advlist lists wordcount autosave"
|
||||
default: "searchreplace visualchars code table nonbreaking lists autosave"
|
||||
},
|
||||
toolbar: {
|
||||
type: [String, Array],
|
||||
// 如果要取消粘贴只粘贴文本,需要用户加格式请加上pastetext
|
||||
default:
|
||||
"code undo redo restoredraft | paste |bold codesample | preview | alignleft alignjustify indent formatpainter | \
|
||||
styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | charmap pagebreak insertdatetime"
|
||||
"code undo redo restoredraft | paste | bold | alignleft alignjustify indent formatpainter | \
|
||||
styleselect formatselect fontselect fontsizeselect | bullist numlist | subscript superscript removeformat"
|
||||
}
|
||||
})
|
||||
|
||||
@@ -71,6 +70,53 @@ let content = computed({
|
||||
|
||||
const list = ref([])
|
||||
|
||||
// 辅助函数:遍历元素和子元素的style样式
|
||||
function cleanStyles(element) {
|
||||
element.removeAttribute("style") // 移除元素自身的style属性
|
||||
element.removeAttribute("class") // 移除元素自身的class属性
|
||||
// 遍历子元素,递归清理样式
|
||||
for (const child of element.children) {
|
||||
// 移除子元素的style以及class属性
|
||||
cleanStyles(child)
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:将元素中span变为text节点
|
||||
function removeUnwantedSpansAndMore(element) {
|
||||
// 所有span变为字符串
|
||||
const spans = element.querySelectorAll("span")
|
||||
for (const span of spans) {
|
||||
const text = span.textContent
|
||||
const textNode = document.createTextNode(text)
|
||||
const parent = span.parentNode
|
||||
parent.removeChild(span)
|
||||
parent.appendChild(textNode)
|
||||
}
|
||||
// 所有a元素变成为字符串
|
||||
const as = element.querySelectorAll("a")
|
||||
for (const a of as) {
|
||||
const text = a.textContent
|
||||
const textNode = document.createTextNode(text)
|
||||
const parent = a.parentNode
|
||||
parent.removeChild(a)
|
||||
parent.appendChild(textNode)
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:去掉注释节点
|
||||
function removeCommentNodes(node) {
|
||||
const childNodes = node.childNodes
|
||||
// 遍历子节点
|
||||
for (let i = childNodes.length - 1; i >= 0; i--) {
|
||||
// 如果是注释节点
|
||||
if (childNodes[i].nodeType === Node.COMMENT_NODE) {
|
||||
node.removeChild(childNodes[i]) // 从父节点中移除该注释节点
|
||||
} else if (childNodes[i].nodeType === Node.ELEMENT_NODE) {
|
||||
removeCommentNodes(childNodes[i]) // 如果是元素节点,递归检查该元素的子节点
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initConfig = reactive({
|
||||
menubar: false, // 菜单栏显隐
|
||||
language_url: "/tinymce/i18n/zh_CN.js",
|
||||
@@ -82,17 +128,30 @@ const initConfig = reactive({
|
||||
toolbar: props.toolbar,
|
||||
skeletonScreen: true,
|
||||
branding: false,
|
||||
paste_as_text: true, // 粘贴文字只能是纯文本
|
||||
content_css: "/tinymce/skins/content/default/content.css",
|
||||
setup: (editor) => {
|
||||
editor.on("init", () => {
|
||||
editor.getBody().style.fontSize = "14px"
|
||||
selector: "#textarea1", // 下面自定义样式选中的区域为编辑区
|
||||
content_style: "body {line-height:1.5;font-size:14px;} p {margin:2px 0px;}", // 这里可以设置自定义样式
|
||||
// paste_as_text: false, // 粘贴文字只能是纯文本
|
||||
// 1.自定义粘贴过滤器函数,args.content就是粘贴内容
|
||||
paste_preprocess: function (plugin, args) {
|
||||
let content = args.content
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(content, "text/html")
|
||||
// 遍历一级元素
|
||||
Array.from(doc.body.children).forEach((element) => {
|
||||
cleanStyles(element)
|
||||
removeUnwantedSpansAndMore(element)
|
||||
removeCommentNodes(element)
|
||||
console.log(element)
|
||||
})
|
||||
// 将处理后的fragment转换回HTML字符串
|
||||
args.content = doc.body.innerHTML
|
||||
}
|
||||
})
|
||||
|
||||
const editorKey = ref(new Date().getTime())
|
||||
|
||||
// 图片作为img的base64处理
|
||||
watch(
|
||||
() => list.value,
|
||||
(imgs) => {
|
||||
|
||||
1
cdTMP/src/env.d.ts
vendored
1
cdTMP/src/env.d.ts
vendored
@@ -1 +1,2 @@
|
||||
declare module "@arco-design/web-vue/dist/arco-vue-icon"
|
||||
declare module 'vue-virtual-scroller'
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
/>
|
||||
</a-space>
|
||||
</div>
|
||||
<div class="center-side flex items-center justify-center">
|
||||
<div class="center-side flex items-center justify-center font-bold text-lg">
|
||||
<template v-if="title"> 项目名称:{{ title }} </template>
|
||||
<Menu v-if="topMenu"></Menu>
|
||||
</div>
|
||||
|
||||
@@ -24,6 +24,7 @@ app.use(router)
|
||||
app.use(pinia)
|
||||
app.use(globalComponents)
|
||||
// app.use(directive)
|
||||
// 虚拟列表
|
||||
|
||||
// 注册ma-icon图标
|
||||
const modules = import.meta.glob("./assets/ma-icons/*.vue", { eager: true })
|
||||
|
||||
@@ -52,7 +52,7 @@ const useTreeDataStore = defineStore("treeDataStore", {
|
||||
this.treeData = roundData.data
|
||||
this.originTreeData = roundData.data
|
||||
},
|
||||
// 新增删除dut后更新树状显示
|
||||
// 新增删除dut后更新树状显示-注意传的key是dut下面的design的key
|
||||
async updateDutTreeData(data, projrctId) {
|
||||
let temp = data.key.split("-")
|
||||
temp.pop(-1)
|
||||
@@ -61,7 +61,7 @@ const useTreeDataStore = defineStore("treeDataStore", {
|
||||
const res = await projectApi.getDutInfo(projrctId, nodeKey, "0")
|
||||
this.treeData[roundKey].children = res.data
|
||||
},
|
||||
// 新增删除designDemand后tree显示
|
||||
// 新增删除designDemand后tree显示-注意传的是测试项的key
|
||||
async updateDesignDemandTreeData(data, projrctId) {
|
||||
let temp = data.key.split("-")
|
||||
temp.pop(-1)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user