semanticsEditPage.vue 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. <template>
  2. <div class="semantics-edit-page">
  3. <div class="semantics-tool page-block-wrap">
  4. <div class="tool-btn" @click="handleBtnClick({type:'search'})">
  5. <img :src="toolIcon['search']" />
  6. 搜索
  7. </div>
  8. <div class="tool-btns">
  9. <el-popover
  10. placement="right"
  11. v-model="isSelectLabelShow"
  12. v-if="compareFiles.length"
  13. trigger="manual">
  14. <select-label-detail
  15. :isSelectLabelShow="isSelectLabelShow"
  16. :lastSelectLabelArr="selectedLabelArr"
  17. :selectedFileArr="selectedFileArr"
  18. :newAddLabelArr="newAddLabelArr"
  19. @close="handleSelectLabel"
  20. />
  21. <div slot="reference" class="tool-btn" style="padding:0 30px !important;border-right: 1px solid #DCDFE6;"
  22. @click="handleBtnClick({type:'selectLabel'},isSelectLabelShow?'close':'open')">
  23. <img :src="toolIcon['eye']" style="width:14px,height:9px"/>
  24. 选择标签
  25. </div>
  26. </el-popover>
  27. <div class="tool-btn" v-for="btn in toolBtnArr" :key="btn.type" @click="handleBtnClick(btn)">
  28. <img :src="toolIcon[btn.icon]" :style="btn.icon==='eye'?{'width':'14px','height':'9px'}:{}"/>
  29. {{btn.text}}
  30. </div>
  31. </div>
  32. </div>
  33. <div class="semantics-content" v-if="compareFiles.length">
  34. <div class="file-page-label-wrap">
  35. <draggable
  36. class="drag-cont"
  37. v-model="compareFiles"
  38. animation="300"
  39. @start="dragStartHandler"
  40. @update="dragEnter"
  41. @end="movePageLabel"
  42. >
  43. <div class="page-label" :class="{'active':currentPageIndex===index}"
  44. v-for="(label,index) in compareFiles" :key="label.DocId"
  45. @click="changePageLabel(index)"
  46. >
  47. <span class="text-ellipsis">{{label.Title}}</span>
  48. <span class="close" @click.stop="deleteCompareFile(label,index)"><i class="el-icon-close"></i></span>
  49. </div>
  50. </draggable>
  51. </div>
  52. <div class="files">
  53. <div class="file-item overflow-hide-scrollbar"
  54. @scroll="hideTextAreaTool('scroll',document)"
  55. :class="{'couple':showingFiles.length===2||showingFiles.length===1}"
  56. v-for="(document,index) in showingFiles" :key="document.DocId">
  57. <div class="file-item-info">
  58. <p class="file-title">{{document.Title}}</p>
  59. <p class="file-sub-info"><!-- <span style="margin-right:30px;">{{document.Theme}}</span> --><span>所属目录:{{document.ClassifyName}}</span></p>
  60. <!-- <span class="close-btn" @click.stop="deleteCompareFile(document,index)"><i class="el-icon-circle-close"></i></span> -->
  61. </div>
  62. <div class="file-item-content" :id="`file-content-${index}`"
  63. v-click-outside="hideTextAreaTool"
  64. @mouseup.stop="e=>{handleMouseUp(e,index)}">
  65. <div class="file-item-block" :class="{'this-block':block.ishasThisLabel,'select-block':block.isSelectLabel}"
  66. v-for="block in document.SectionList" :key="block.SectionId"
  67. >
  68. <!-- @mousemove="e=>checkTextArea(e,document)" -->
  69. <p class="block-innerText" :id="block.SectionId">{{block.innerText}}</p>
  70. <div class="file-item-icon">
  71. <el-popover
  72. placement="right"
  73. trigger="hover">
  74. <section-label-detail
  75. :detailArr="[
  76. {title:'当前标签',isThis:1,arr:block.isThisLabelArr||[]},
  77. {title:'历史标签(我的)',isThis:0,arr:block.isHistoryLabelArr||[]},
  78. {title:'Ta的标签',isThis:0,arr:block.isOtherLabelArr||[]},
  79. ]"
  80. @deleteLab="(label)=>{deleteSelectLabel(document,block,label)}"
  81. @addLab="(label)=>{quickAddSelectLabel(document,block,label)}"
  82. />
  83. <span slot="reference" @click.stop><img :src="require(`@/assets/img/document_m/menu.svg`)"></span>
  84. </el-popover>
  85. <span class="label" @click.stop="handleAddSelectionLabel(document,block)"><img :src="require(`@/assets/img/document_m/label.svg`)"></span>
  86. </div>
  87. </div>
  88. <!-- 高亮方案的兼容版本
  89. <highlight-canvas
  90. ref="HighlightCanvas"
  91. :parentDomId="`file-content-${index}`"
  92. :parentIndex="index"/> -->
  93. </div>
  94. </div>
  95. </div>
  96. </div>
  97. <div class="semantics-content" v-if="compareFiles.length===0">
  98. <tableNoData text="暂无信息" class="empty"/>
  99. </div>
  100. <!-- 选择对比文档弹窗 -->
  101. <select-file-dialog
  102. :isSelectFileShow="isSelectFileShow"
  103. :selectedFileArr="selectedFileArr"
  104. @selectFile="AddCompareFiles"
  105. @close="handleBtnClick({type:'selectFile'},'close')"
  106. />
  107. <!-- 打标签弹窗 -->
  108. <add-section-label-dialog
  109. :isAddSectionLabelShow="isAddSectionLabelShow"
  110. :sectionInfo="sectionInfo"
  111. @addSection="addSectionLabel"
  112. @close="handleBtnClick({type:'addSectionLabel'},'close')"
  113. />
  114. <!-- 搜索框弹窗 -->
  115. <search-box
  116. ref="searchBox"
  117. :isSearchBoxShow="isSearchBoxShow"
  118. @close="handleBtnClick({type:'search'},'close')"
  119. />
  120. <!-- 保存/另存为+上传图片弹窗 -->
  121. <save-semantic-dialog
  122. ref="saveDialog"
  123. :isSaveFileShow="isSaveFileShow"
  124. :semanticInfo="semanticInfo"
  125. :saveType="saveType"
  126. :resultData="resultData"
  127. :dialogLoading="dialogLoading"
  128. @saveSemantic="saveSemantic"
  129. @uploadImg="uploadImg"
  130. @close="checkSemanticId"
  131. />
  132. <!-- 选定文字操作栏 -->
  133. <select-text-area-tool
  134. v-show="isShowTextArea"
  135. ref="SelectTextAreaTool"
  136. :isShowTextArea="isShowTextArea"
  137. :positionInfo="positionInfo"
  138. :textAreaInfo="textAreaInfo"
  139. :currentTextArea="currentTextArea"
  140. @addTextAreaLabel="addTextAreaLabel"
  141. @quickAddTextAreaLabel="quickAddTextAreaLabel"
  142. @deleteTextAreaLabel="deleteTextAreaLabel"
  143. />
  144. </div>
  145. </template>
  146. <script>
  147. /* js */
  148. import {toolIcon,toolBtnArr,dialogMap,textAreaToolWidth} from '../utils/config';
  149. import {formatSemanticFile,formatCompareListItem,
  150. checkSelectionAndRange,getCurrentRange,
  151. getTextRange,updateLabelList,
  152. addNewTextRange,checkIsSelectLabelInFile,
  153. checkAllLabelsAreEmpty} from '../utils/index';
  154. /*components */
  155. import draggable from 'vuedraggable';
  156. import selectFileDialog from './components/selectFileDialog.vue';
  157. import AddSectionLabelDialog from './components/addSectionLabelDialog.vue';
  158. import SectionLabelDetail from './components/sectionLabelDetail.vue';
  159. import SelectLabelDetail from './components/selectLabelDetail.vue';
  160. import SaveSemanticDialog from './components/saveSemanticDialog.vue';
  161. import SearchBox from './components/searchBox.vue';
  162. import HighlightCanvas from './components/highlightCanvas.vue';
  163. import SelectTextAreaTool from './components/selectTextAreaTool.vue';
  164. /* api */
  165. import {semanticInterface} from '@/api/modules/semanticsApi.js';
  166. export default {
  167. components: {
  168. draggable,selectFileDialog, AddSectionLabelDialog,
  169. SectionLabelDetail, SelectLabelDetail, SaveSemanticDialog,
  170. SearchBox,HighlightCanvas, SelectTextAreaTool
  171. },
  172. directives: {
  173. 'click-outside':{
  174. bind(el, binding) {
  175. const clickHandle = (e)=>{
  176. //点击file-item-content之外的地方,隐藏选定文字操作栏
  177. const legalClassNameArr = ['file-item-content','block-innerText','select-text-area-tool']
  178. const SelectTextAreaTool = document.querySelector('.select-text-area-tool')
  179. if(el.contains(e.target)||legalClassNameArr.includes(e.target.className)||SelectTextAreaTool.contains(e.target)){
  180. return false
  181. }
  182. if(binding.value && typeof binding.value === 'function'){
  183. binding.value(_,_,e)
  184. }
  185. }
  186. el.__click__outside = clickHandle
  187. document.addEventListener('click',clickHandle)
  188. },
  189. unbind(el,binding,vnode){
  190. document.removeEventListener('click',el.__click__outside)
  191. }
  192. },
  193. },
  194. data() {
  195. return {
  196. /* button 页面操作按钮相关*/
  197. toolIcon:toolIcon,//操作栏
  198. selectedLabelArr:[],//选择标签-已选择的标签
  199. /* document-files 文档相关*/
  200. selectedFileArr:[],//已选择的文档ids
  201. compareFiles:[],//对比的文档
  202. showingFiles:[],//展示在可视区的文档 是compareFiles中的其中三个
  203. /* block and block-label 段落及段落标签相关*/
  204. newAddLabelArr:[],//当前页(段落)新打的标签
  205. sectionInfo:{},//当前选择的段落
  206. /* page-label 标签页 */
  207. currentPageIndex:0,//当前选择的标签页,与showingFiles[0]对应
  208. dragStartIndex:0,//拖拽标签页的标识
  209. /* semantic 文档对比信息 */
  210. semanticId:0,//文档对比id,为0时是新增
  211. saveType:0,//0 另存为 1保存
  212. resultData:{},//文档对比保存的结果,用于生成预览图片
  213. semanticInfo:{},//语义分析信息
  214. /* textArea text-label 选定文字及选定文字标签 */
  215. blockInfo:{SectionId:'',DocId:''},//选定文字时,选区所属的段落信息,为document.SectionList的其中一项
  216. positionInfo:{top:0,left:0},//选定文字时,操作栏应展示的位置
  217. textAreaInfo:[],//选定文字时,该选定文字区域信息
  218. currentTextArea:{},//当前选择的textArea textAreaInfo中的某一项
  219. isShowTextArea:false,//控制选定文字操作栏展示
  220. /* dialog */
  221. isSearchBoxShow:false,//搜索框弹窗
  222. isSelectLabelShow:false,//全部标签弹窗
  223. isSelectFileShow:false,//选择对比文档弹窗
  224. isSaveFileShow:false,//保存弹窗
  225. isAddSectionLabelShow:false,//打标签弹窗
  226. dialogLoading:false,//保存弹窗用的
  227. };
  228. },
  229. computed:{
  230. toolBtnArr(){//操作栏
  231. //新增
  232. if(this.semanticId===0){
  233. return this.compareFiles.length?toolBtnArr.slice(0,-1):[toolBtnArr[0]]
  234. }else{
  235. //编辑
  236. return toolBtnArr
  237. }
  238. },
  239. },
  240. methods: {
  241. //获取语义分析详情
  242. getSemanticData(){
  243. const {fileId} = this.$route.query
  244. if(fileId&&Number(fileId.split('_')[1])){
  245. this.semanticId = Number(fileId.split('_')[1])
  246. semanticInterface.getSemanticDetail({
  247. CompareId:this.semanticId
  248. }).then(res=>{
  249. if(res.Ret!==200) return
  250. this.initSemantic(res.Data)
  251. })
  252. }else{
  253. this.semanticId = 0
  254. this.semanticInfo = {
  255. SaCompareId:this.semanticId,
  256. Title:'',
  257. ClassifyId:''
  258. }
  259. }
  260. },
  261. //初始化语义分析
  262. initSemantic(data){
  263. const {SaCompareId,Title,ClassifyId,KeywordsList} = data
  264. this.semanticInfo = {SaCompareId,Title,ClassifyId}
  265. const {DocList} = data
  266. //初始化 selectedFileArr
  267. this.selectedFileArr = DocList.map(doc=>{
  268. return doc.DocId
  269. })
  270. //初始化 compareFiles
  271. this.compareFiles = DocList?DocList.map(doc=>{
  272. doc = formatSemanticFile(doc,this.selectedLabelArr)
  273. return doc
  274. }):[]
  275. //初始化 showingFiles
  276. this.initShowingFiles()
  277. //初始化搜索词
  278. if(KeywordsList.length){
  279. this.$refs.searchBox.searchArr = KeywordsList
  280. }
  281. },
  282. //点击工具栏按钮
  283. handleBtnClick(btn,operate='open'){
  284. const {type} = btn
  285. this.saveType = type==='saveOther'?0:1
  286. this[dialogMap[type]] = operate==='open'?true:false
  287. if(operate==='close'){
  288. this.dialogLoading = false
  289. }
  290. },
  291. /* 移动标签相关 */
  292. dragStartHandler({oldIndex}){
  293. //console.log('start',oldIndex)
  294. this.currentPageIndex = oldIndex
  295. this.dragStartIndex = oldIndex
  296. },
  297. //拖动后 compareFiles的顺序也会改变
  298. dragEnter({newIndex}){},
  299. movePageLabel({newIndex}){
  300. //console.log('end',newIndex)
  301. if(this.dragStartIndex===newIndex) return
  302. //切换标签
  303. this.changePageLabel(newIndex)
  304. },
  305. /*--------- */
  306. //切换文档标签页
  307. changePageLabel(index){
  308. //切换当前标签索引
  309. this.currentPageIndex = index
  310. //标签视口宽度
  311. const dragContWidth = $('.drag-cont').width()
  312. //单个标签宽度()
  313. const pageLabelWidth = $('.page-label').outerWidth()
  314. //标签总宽度(包括不可见部分)
  315. const pageLabelTotalWidth = pageLabelWidth*this.compareFiles.length
  316. //当前偏移位置
  317. const dragMarginLeft = Number($('.drag-cont').css('marginLeft').slice(0,-2))
  318. //当前选择标签在drag-cont的位置(加上偏移修正)
  319. const currentPosition = pageLabelWidth*index+dragMarginLeft
  320. //如果当前选择标签的前后三个不在视口,则移动确保这7个标签在视口处
  321. const safeWidth = pageLabelWidth*3
  322. //currentPosition>dragContWidth-safeWidth marginLeft为负
  323. const minusMargin = (currentPosition>(dragContWidth-safeWidth))
  324. //currentPosition<pageLabelWidth*2 marginLeft为正
  325. const plusMargin = (currentPosition<safeWidth)
  326. //但|marginLeft|+dragContWidth<=pageLabelTotalWidth
  327. const isMove = (dragContWidth<pageLabelTotalWidth)&&(minusMargin||plusMargin)
  328. //打印只是为了调试
  329. /* console.table([{
  330. 'pageIndex':index,
  331. 'dragContWidth':dragContWidth,
  332. 'pageLabelWidth':pageLabelWidth,
  333. 'pageLabelTotalWidth':pageLabelTotalWidth,
  334. 'dragMarginLeft':dragMarginLeft,
  335. 'currentPosition':currentPosition,
  336. 'minusMargin':minusMargin,
  337. 'plusMargin':plusMargin,
  338. }]) */
  339. if(isMove){
  340. let isLimit = pageLabelWidth*3+dragContWidth+Math.abs(dragMarginLeft)>=pageLabelTotalWidth
  341. let marginLeft = minusMargin&&isLimit?pageLabelTotalWidth-dragContWidth:pageLabelWidth*3
  342. const indexLimit = this.compareFiles.length<=10||index<3
  343. marginLeft = (plusMargin&&isLimit)||indexLimit?0:pageLabelWidth*3
  344. marginLeft = minusMargin?marginLeft*-1:marginLeft
  345. //console.log('isLimit',isLimit)
  346. //console.log('move',marginLeft)
  347. $('.drag-cont').css('marginLeft',isLimit?marginLeft:(marginLeft+dragMarginLeft))
  348. }
  349. //更新视口文档
  350. this.initShowingFiles()
  351. },
  352. //初始化展示视口文档
  353. initShowingFiles(){
  354. //切换前先关掉搜索框
  355. this.handleBtnClick({type:'search'},'close')
  356. //showingFiles为currentPageIndex,currentPageIndex+1,currentPageIndex+2的三个文档
  357. const currentPageIndex = this.currentPageIndex
  358. if(this.compareFiles.length===0){
  359. this.showingFiles = []
  360. return
  361. }
  362. //判断currentPageIndex+1,currentPageIndex+2是否越界
  363. if(currentPageIndex===this.compareFiles.length-1){
  364. this.showingFiles = [this.compareFiles[currentPageIndex]]
  365. }else if(currentPageIndex===this.compareFiles.length-2){
  366. this.showingFiles = [this.compareFiles[currentPageIndex],this.compareFiles[currentPageIndex+1]]
  367. }else{
  368. this.showingFiles = [this.compareFiles[currentPageIndex],this.compareFiles[currentPageIndex+1],this.compareFiles[currentPageIndex+2]]
  369. }
  370. this.$nextTick(()=>{
  371. //初始化showingFiles中选定文字标签
  372. const textAreaRange = []
  373. const ortherTextAreaRange = []
  374. this.showingFiles.forEach(file=>{
  375. if(file.textAreaList){
  376. file.textAreaList = file.textAreaList.map(text=>{
  377. const {rangeInfo,range} = getTextRange(text)
  378. text.areaRangeInfo = rangeInfo
  379. if(text.ishasThisLabel){
  380. textAreaRange.push(range)
  381. }
  382. if(text.isSelectLabel){
  383. ortherTextAreaRange.push(range)
  384. }
  385. return text
  386. })
  387. }
  388. })
  389. this.$nextTick(()=>{
  390. if(!CSS.highlights){
  391. this.$message.warning('该浏览器不支持高亮效果,请切换至chrome浏览器105版本以上')
  392. return
  393. }
  394. CSS.highlights&&CSS.highlights.set('show-result',new Highlight(...textAreaRange))
  395. CSS.highlights&&CSS.highlights.set('show-result-other',new Highlight(...ortherTextAreaRange))
  396. })
  397. /* //重新渲染highlightCanvas
  398. const canvasList = this.$refs.HighlightCanvas
  399. canvasList.forEach(canvas=>{
  400. canvas.initCanvasStyle()
  401. }) */
  402. //重新渲染搜索文字
  403. this.$refs.searchBox.searchWord(_,'research')
  404. })
  405. },
  406. //点击段落打标签icon
  407. handleAddSelectionLabel(file,block){
  408. const {DocId} = file
  409. const {SectionId,isThisLabelArr} = block
  410. //获取段落信息
  411. this.sectionInfo = {
  412. DocId,SectionId,isThisLabelArr
  413. }
  414. this.isAddSectionLabelShow = true
  415. },
  416. //点击上方选择标签icon
  417. handleSelectLabel({type,selectedLabelArr}){
  418. if(type!=='close'){
  419. this.selectedLabelArr = selectedLabelArr
  420. this.compareFiles = this.compareFiles.map(doc=>{
  421. //检测文档标签是否有被筛选的,有则将段落/片段的isSelectLabel设置为true
  422. doc = checkIsSelectLabelInFile(doc,this.selectedLabelArr)
  423. return doc
  424. })
  425. //更新视口文档
  426. this.initShowingFiles()
  427. }
  428. this.isSelectLabelShow = false
  429. },
  430. //段落打标签,可以一次打多个
  431. addSectionLabel(labelArr){
  432. //console.log('addArr',labelArr)
  433. //转换拿到的标签格式
  434. const labelList = labelArr.map(item=>{
  435. let temp = {
  436. LabelId :item.SaLabelId||item.LabelId,
  437. LabelName:item.LabelName,
  438. IsHistory:0,IsOther:0,
  439. IsThis:1,
  440. }
  441. return temp
  442. })
  443. this.updateSectionLabel(labelList,this.sectionInfo)
  444. this.checkLabel(labelList)
  445. this.isAddSectionLabelShow = false
  446. },
  447. //删除当前段落标签
  448. deleteSelectLabel(document,block,label){
  449. //获取当前段落的LabelList isThisLabelArr
  450. const {LabelList,isThisLabelArr} = block
  451. //labelList为isThisLabelArr删掉label这一项的数组
  452. const labelList = isThisLabelArr.filter(l=>l.LabelId!==label.LabelId)
  453. //LabelList对应的项要将IsThis改为0,存在IsThis IsHistory IsOther三者都为0的label不影响展示和数据,可以不管
  454. const newLabelList = LabelList.map(l=>{
  455. if(l.LabelId===label.LabelId&&l.IsThis===1){
  456. l.IsThis=0
  457. }
  458. return l
  459. })
  460. //updateSectionLabel type = 'delete'
  461. this.updateSectionLabel(labelList,{
  462. DocId:document.DocId,
  463. SectionId:block.SectionId
  464. },'delete',newLabelList)
  465. },
  466. //段落快速打标签,一次打一个
  467. quickAddSelectLabel(document,block,label){
  468. //获取当前段落的isThisLabelArr
  469. const {isThisLabelArr} = block
  470. //判断label在isThisLabelArr有没有,没有就 addSectionLabel
  471. if(isThisLabelArr.find(l=>l.LabelId===label.LabelId)){
  472. return
  473. }
  474. this.sectionInfo = {
  475. DocId:document.DocId,
  476. SectionId:block.SectionId,
  477. isThisLabelArr
  478. }
  479. this.addSectionLabel([...isThisLabelArr,label])
  480. },
  481. //更新段落标签
  482. updateSectionLabel(labelList,{DocId,SectionId},type='add',LabelList=[]){
  483. //找到更新的是哪篇文章
  484. let fileIndex = 0
  485. const file = this.compareFiles.find((file,index)=>{
  486. fileIndex=index
  487. return file.DocId===DocId
  488. })
  489. //找到更新的是哪个段落
  490. let sectionIndex=0
  491. const section = file.SectionList.find((section,index)=>{
  492. sectionIndex=index
  493. return section.SectionId===SectionId
  494. })
  495. if(!section) return
  496. //更新段落的标签信息,实际上更换的只有isThisLabelArr 和LabelList中对应的部分
  497. //其他两个数组不会做更改
  498. section.isThisLabelArr = labelList||[]
  499. section.ishasThisLabel = labelList.length?true:false
  500. if(type==='add'){
  501. //需要去重和更新LabelList的对应项
  502. const {LabelList,isThisLabelArr} = section
  503. section.LabelList = updateLabelList(LabelList,isThisLabelArr)
  504. }else{
  505. section.LabelList = LabelList
  506. }
  507. //更新段落和文章
  508. file.SectionList.splice(sectionIndex,1,section)
  509. this.compareFiles.splice(fileIndex,1,file)
  510. },
  511. //检查是否有新加的标签,如果有 更新newAddLabelArr
  512. checkLabel(arr){
  513. arr.forEach(label=>{
  514. if(this.newAddLabelArr.findIndex(l=>l.LabelId===label.LabelId)===-1){
  515. this.newAddLabelArr.push(label)
  516. }
  517. })
  518. },
  519. //添加对比文档
  520. AddCompareFiles(fileKeys){
  521. //获取新选的文档标签 HeadLabel 按理说 应该与selectedLabelArr合并去重
  522. //但是打开selectedLabel的时候会重新请求一次选择对比文档详情 所以这里可以什么都不用做
  523. semanticInterface.getCompareDocumentDetail({
  524. DocIds:fileKeys.join(',')
  525. }).then(res=>{
  526. if(res.Ret!==200) return
  527. const {DocList} = res.Data
  528. const newCompareFiles = DocList.map(doc=>{
  529. return formatSemanticFile(doc,this.selectedLabelArr)
  530. })
  531. this.compareFiles = [...this.compareFiles,...newCompareFiles]
  532. this.selectedFileArr = [...this.selectedFileArr,...fileKeys]
  533. this.initShowingFiles()
  534. //最后关闭这个弹窗
  535. this.handleBtnClick({type:'selectFile'},'close')
  536. })
  537. },
  538. //删除对比文档
  539. deleteCompareFile({DocId},index){
  540. //更新currentPageIndex
  541. const length = this.compareFiles.length
  542. if(length===1){
  543. this.$message.warning('请至少保留一个文档')
  544. return
  545. }
  546. if(this.currentPageIndex===length-1){
  547. this.currentPageIndex--
  548. }
  549. //compareFiles删除一项
  550. const fileIndex = this.compareFiles.findIndex(f=>f.DocId===DocId)
  551. this.compareFiles.splice(fileIndex,1)
  552. this.initShowingFiles()
  553. //selectedFileArr删除fileid
  554. const selectedIndex = this.selectedFileArr.findIndex(f=>f===DocId)
  555. this.selectedFileArr.splice(selectedIndex,1)
  556. //隐藏选定文字操作栏
  557. this.hideTextAreaTool()
  558. },
  559. //保存/另存为语义分析
  560. saveSemantic(semanticInfo){
  561. const{SaCompareId,Title,ClassifyId} = semanticInfo
  562. //参数格式转换
  563. const CompareList = this.compareFiles.map(file=>{
  564. return formatCompareListItem(file)
  565. })
  566. /* console.log('result',CompareList)
  567. return */
  568. this.dialogLoading = true
  569. semanticInterface.saveSemantic({
  570. SaCompareId:this.saveType===0?0:Number(SaCompareId),
  571. Title,ClassifyId:Number(ClassifyId),
  572. CompareList
  573. }).then(async (res)=>{
  574. this.dialogLoading = false
  575. if(res.Ret!==200) return
  576. const hintWord = this.saveType===0?'另存为':SaCompareId?'保存':'新增'
  577. this.$message.success(`${hintWord}成功`)
  578. this.semanticId = res.Data.SaCompareId
  579. this.semanticInfo.SaCompareId = res.Data.SaCompareId
  580. this.semanticInfo.Title = Title
  581. this.semanticInfo.ClassifyId = ClassifyId
  582. this.resultData = res.Data
  583. //检查是否有搜索词,有则保存搜索词
  584. const searchArr = this.$refs.searchBox.searchArr
  585. await semanticInterface.saveKeyWords({
  586. SaCompareId:this.semanticId,
  587. Keywords:searchArr
  588. })
  589. this.$refs.saveDialog.goNext()
  590. })
  591. },
  592. //保存/另存为弹窗关闭时触发
  593. //检查当前页面的语义分析id是否一致,不一致则replace对应的详情页
  594. checkSemanticId(type){
  595. //console.log('type',type)
  596. //console.log('show',this.isSaveFileShow)
  597. const {SaCompareId,ClassifyId} = this.semanticInfo
  598. const fileId = this.$route.query.fileId
  599. //type为toList时,需要跳转回列表页
  600. //关闭弹窗时,会触发两次close事件,第一次是按钮触发,第二次是父级组件改变了isSaveFileShow触发
  601. //第一次的type为toList,第二次的type为undefined,因为需要跳转,所以将这段逻辑提前并return
  602. if(type==='toList'){
  603. sessionStorage.setItem('fileClassify',ClassifyId+'')
  604. sessionStorage.setItem('fileId',SaCompareId+'')
  605. this.$router.replace('/semanticsPage')
  606. return
  607. }
  608. //edit的情况
  609. if(SaCompareId&&fileId&&Number(this.$route.query.fileId.split('_')[1])!==SaCompareId){
  610. this.$router.replace({path:'/editSemantics',query:{fileId:'children_'+SaCompareId}})
  611. }
  612. //add的情况
  613. if(this.$route.path==='/addSemantics'&&SaCompareId){
  614. this.$router.replace({path:'/editSemantics',query:{fileId:'children_'+SaCompareId}})
  615. }
  616. this.handleBtnClick({type:'saveFile'},'close')
  617. },
  618. //上传结果图片
  619. async uploadImg(imgData){
  620. //将base64上传获得线上地址
  621. const form = new FormData();
  622. form.append('Image', imgData);
  623. const res = await semanticInterface.uploadImg(form)
  624. if(res.Ret!==200) return
  625. //上传结果图片
  626. semanticInterface.updateSemanticImg({
  627. SaCompareId:this.semanticId,
  628. ResultImg:res.Data.ResourceUrl
  629. }).then(res=>{
  630. if(res.Ret!==200) return
  631. this.$message.success('上传结果图片成功')
  632. this.checkSemanticId('toList')
  633. })
  634. },
  635. //在file-item-content区域鼠标抬起触发
  636. handleMouseUp(e,index){
  637. if(e.type!=="mouseup") return
  638. //关闭选定文字操作栏
  639. this.hideTextAreaTool()
  640. const selection = document.getSelection()
  641. const file = this.showingFiles[index]
  642. //获取该段落的选定文字标签
  643. if(!checkSelectionAndRange(selection)){
  644. //不是选区,单纯的鼠标点击后抬起
  645. if(e.target.nodeName==='P'&&e.target.className.includes('block-innerText')){
  646. const blockId = Number(e.target.id)||0
  647. blockId&&this.checkPosTextLabel(e,file,blockId)
  648. }
  649. return
  650. }
  651. //是选区
  652. this.getSelectionRange(selection,file)
  653. },
  654. //检测位置是否有选定文字标签,若有则展示操作栏
  655. checkPosTextLabel({clientX,clientY},file,SectionId){
  656. //获取该段落的选定文字标签
  657. const textAreaList = file.textAreaList.filter(t=>t.SectionId===SectionId)
  658. const block = file.SectionList.find(s=>s.SectionId===SectionId&&s.IsPart===0)
  659. const textAreaInfo = []
  660. //判断文字标签是否在点击位置内
  661. textAreaList.forEach(text=>{
  662. if(text.areaRangeInfo){
  663. const {BoundingXRange,BoundingYRange} = text.areaRangeInfo
  664. const isBoundingX = clientX>=BoundingXRange[0]&&clientX<=BoundingXRange[1]
  665. const isBoundingY = clientY>=BoundingYRange[0]&&clientY<=BoundingYRange[1]
  666. isBoundingX&&isBoundingY&&(textAreaInfo.push(text))
  667. }
  668. })
  669. /* console.log('arr',textAreaInfo) */
  670. if(textAreaInfo.length){
  671. //根据最大范围确定操作栏应展示的位置
  672. const maxWidth = Math.max.apply(null,textAreaInfo.map(i=>i.areaRangeInfo.rangeWidth))
  673. const maxTextRange = textAreaInfo.find(item=>item.areaRangeInfo.rangeWidth===maxWidth)
  674. this.positionInfo = {
  675. left:maxTextRange.areaRangeInfo.BoundingXRange[0]+maxWidth/2 - textAreaToolWidth/2,
  676. top:maxTextRange.areaRangeInfo.BoundingYRange[1]
  677. }
  678. this.blockInfo = {DocId:file.DocId,...block}
  679. this.currentTextArea = maxTextRange
  680. this.textAreaInfo = textAreaInfo
  681. this.isShowTextArea = true
  682. }else{
  683. this.hideTextAreaTool()
  684. }
  685. },
  686. //获取选区并展示操作栏
  687. getSelectionRange(selection,file){
  688. const range = selection.getRangeAt(0)
  689. //因为鼠标点击选区时选区会消失,重新渲染一次
  690. selection.removeAllRanges()
  691. selection.addRange(range)
  692. const {currentRange,currentNode} = getCurrentRange(selection,range)
  693. //根据id找到选定文字所属段落
  694. const SectionId = Number(currentNode.parentNode.id)
  695. const block = file.SectionList.find(s=>s.SectionId===SectionId)
  696. this.blockInfo = {...block,DocId:file.DocId}
  697. //根据currentRange确定选定文字操作栏应该出现的位置
  698. const bounding = currentRange.getClientRects()
  699. this.positionInfo = {
  700. left:bounding[bounding.length-1].left+bounding[bounding.length-1].width/2 - textAreaToolWidth/2,
  701. top:bounding[bounding.length-1].top+bounding[bounding.length-1].height
  702. }
  703. //根据currentRange确定是选区是否已有标签 并展示操作栏
  704. this.checkOffsetPosTextLabel(currentRange,file,SectionId,currentRange.toString())
  705. //无论是否有标签,都需要展示操作栏
  706. this.isShowTextArea = true
  707. },
  708. //检测段落指定位置是否有选定文字标签
  709. checkOffsetPosTextLabel({startOffset,endOffset},file,SectionId,rangeString){
  710. //获取该段落的选定文字标签
  711. const textAreaList = file.textAreaList.filter(t=>t.SectionId===SectionId)
  712. const textAreaInfo = []
  713. textAreaList.forEach(text=>{
  714. if(text.areaRangeInfo){
  715. const {StartOffset,EndOffset} = text.areaRangeInfo
  716. const isIncludeStart = startOffset<=StartOffset
  717. const isIncludeEnd = endOffset<=EndOffset
  718. isIncludeStart&&isIncludeEnd&&(textAreaInfo.push(text))
  719. }
  720. })
  721. this.textAreaInfo = textAreaInfo
  722. if(this.textAreaInfo.length){
  723. //根据最大范围确定currentTextArea
  724. const maxOffset = Math.max.apply(null,textAreaInfo.map(i=>i.areaRangeInfo.rangeOffset))
  725. const maxTextRange = textAreaInfo.find(item=>item.areaRangeInfo.rangeOffset===maxOffset)
  726. this.currentTextArea = maxTextRange
  727. }else{
  728. //如果当前指定位置内,没有选定文字标签,则新生成一个作为currentTextArea
  729. this.currentTextArea = addNewTextRange(Number(SectionId),startOffset,endOffset,rangeString)
  730. /* const fileIndex = this.compareFiles.findIndex(f=>f.DocId===file.DocId)
  731. file.textAreaList.push(_.cloneDeep(this.currentTextArea))
  732. this.compareFiles.splice(fileIndex,1,file) */
  733. }
  734. },
  735. //隐藏选定文字操作栏,并清除选区
  736. hideTextAreaTool(type,doc,e){
  737. //页面滚动触发,需要判断滚动的文章是否是当前选定文字操作框对应的文章
  738. //如果不是,啥都不做;如果是,隐藏操作框
  739. if(type==='scroll'){
  740. if(!this.isShowTextArea) return
  741. if(doc.DocId===this.blockInfo.DocId){
  742. document.getSelection().removeAllRanges()
  743. this.isShowTextArea = false
  744. }
  745. }else{
  746. //点击页面外的区域触发,隐藏操作栏
  747. //document.getSelection().removeAllRanges()
  748. this.isShowTextArea = false
  749. }
  750. },
  751. //选定文字打标签,一次可以打多个/零个 零个的时候单独处理
  752. addTextAreaLabel(labelArr){
  753. //更改currentTextArea
  754. this.currentTextArea.isThisLabelArr = labelArr
  755. this.currentTextArea.ishasThisLabel = labelArr.length?true:false
  756. const {LabelList,isThisLabelArr} = this.currentTextArea
  757. this.currentTextArea.LabelList = updateLabelList(LabelList,isThisLabelArr)
  758. //更改file.textAreaList[]
  759. const {startOffset,endOffset} = this.currentTextArea
  760. const textAreaListItem = _.cloneDeep(this.currentTextArea)
  761. const {DocId,SectionId} = this.blockInfo
  762. let fileIndex = 0
  763. const file = this.compareFiles.find((file,index)=>{
  764. fileIndex=index
  765. return file.DocId===DocId
  766. })
  767. const textAreaListIndex = file.textAreaList.findIndex(i=>{
  768. return i.startOffset===startOffset&&i.endOffset===endOffset&&i.SectionId===SectionId
  769. })
  770. textAreaListIndex!==-1&&labelArr.length!==0&&file.textAreaList.splice(textAreaListIndex,1,textAreaListItem)
  771. textAreaListIndex===-1&&labelArr.length!==0&&file.textAreaList.push(textAreaListItem)
  772. //labelArr为空时,检查是否应该删除这一项
  773. if(textAreaListIndex!==-1&&labelArr.length===0){
  774. checkAllLabelsAreEmpty(textAreaListItem)&&
  775. file.textAreaList.splice(textAreaListIndex,1)
  776. }
  777. //更改textAreaInfo
  778. const textAreaInfoIndex = this.textAreaInfo.findIndex(i=>{
  779. return i.startOffset===startOffset&&i.endOffset===endOffset&&i.SectionId===SectionId
  780. })
  781. textAreaInfoIndex!==-1&&labelArr.length!==0&&this.textAreaInfo.splice(textAreaInfoIndex,1,textAreaListItem)
  782. textAreaInfoIndex===-1&&labelArr.length!==0&&this.textAreaInfo.push(textAreaListItem)
  783. //labelArr为空时,检查是否应该删除这一项
  784. if(textAreaInfoIndex!==-1&&labelArr.length===0){
  785. checkAllLabelsAreEmpty(textAreaListItem)&&
  786. this.textAreaInfo.splice(textAreaInfoIndex,1)
  787. }
  788. //initShowingFiles
  789. this.compareFiles.splice(fileIndex,1,file)
  790. this.initShowingFiles()
  791. this.checkLabel(labelArr)
  792. },
  793. //选定文字快速打标签,一次只能打一个
  794. quickAddTextAreaLabel(label){
  795. //获取当前选定文字的isThisLabelArr 步骤与段落快速打标签基本相同
  796. const {isThisLabelArr} = this.currentTextArea
  797. if(isThisLabelArr.find(l=>l.LabelId===label.LabelId)){
  798. return
  799. }
  800. this.addTextAreaLabel([...isThisLabelArr,label])
  801. },
  802. //删除选定文字标签
  803. deleteTextAreaLabel(label){
  804. //检查label.areaRangeInfo,rangeSectionId与currentTextArea是否匹配
  805. const {rangeSectionId,areaRangeInfo} = label
  806. const {startOffset,endOffset} = this.currentTextArea
  807. if(!(startOffset===areaRangeInfo.startOffset&&
  808. endOffset===areaRangeInfo.endOffset&&
  809. rangeSectionId===this.currentTextArea.SectionId)
  810. ){
  811. const textAreaInfoIndex = this.textAreaInfo.findIndex(i=>{
  812. return i.startOffset===areaRangeInfo.StartOffset&&i.endOffset===areaRangeInfo.EndOffset&&i.SectionId===rangeSectionId
  813. })
  814. textAreaInfoIndex!==-1&&(this.currentTextArea = this.textAreaInfo[textAreaInfoIndex])
  815. }
  816. const {isThisLabelArr} = this.currentTextArea
  817. const labelList = isThisLabelArr.filter(l=>l.LabelId!==label.LabelId)
  818. this.addTextAreaLabel(labelList)
  819. },
  820. //鼠标是否经过有选区的位置
  821. /* checkTextArea(e,doc){
  822. console.log('move',e)
  823. } */
  824. },
  825. mounted(){
  826. this.getSemanticData()
  827. },
  828. destroyed(){
  829. CSS.highlights&&CSS.highlights.clear();
  830. }
  831. };
  832. </script>
  833. <style lang="scss">
  834. .semantics-edit-page{
  835. .el-popover__reference{
  836. width: 100%;
  837. display: block;
  838. height: 100%;
  839. padding: 0 !important;
  840. }
  841. }
  842. </style>
  843. <style scoped lang="scss">
  844. @import "../css/basePage.scss";
  845. @import "../css/semanticsEditPage.scss";
  846. </style>