瀏覽代碼

add: core层添加;交互优化

wzl 2 月之前
父節點
當前提交
b10f364217

+ 2 - 2
packages/core/src/annotation/layer.js

@@ -321,7 +321,7 @@ class ComPDFAnnotationLayer {
         eventBus: this.eventBus,
         selectedElementName: this.selectedElementName
       })
-    } else if (tool === 'redaction') {
+    } else if (tool === 'redaction' || tool === 'remove') {
       document.querySelector('.document').classList.add('annotation-edit')
       this.resetManagers('redactionManager')
       if (this.redactionManager) {
@@ -858,7 +858,7 @@ class ComPDFAnnotationLayer {
         messageHandler: this.messageHandler
       })
       this.annotationsArray.push(signatureFields)
-    } else if (annotation.type === 'redaction' && !annotation.isDelete) {
+    } else if ((annotation.type === 'redaction' || annotation.type === 'remove') && !annotation.isDelete) {
       const redaction = new Redaction({
         container: this.div,
         annotation,

+ 6 - 8
packages/core/src/annotation/paint/redaction.js

@@ -101,7 +101,7 @@ export default class PaintRedaction {
 
       const annotationData = {
         operate: "add-annot",
-        type: 'redaction',
+        type: this._tool,
         pageIndex: this.page,
         date: new Date()
       }
@@ -156,8 +156,7 @@ export default class PaintRedaction {
       if (!this.linkEle) {
         const linkEle = createElement('div', {
           'display': 'block',
-          'backgroundColor': '#93B9FD',
-          'opacity': '0.3',
+          'border': `${2 * scale}px solid #FF0000`,
           'position': 'absolute'
         })
         this.linkEle = linkEle
@@ -202,13 +201,12 @@ export default class PaintRedaction {
 
   calculate (start, end) {
     const initRect = this.rectCalc({ start, end });
-    const actualbdwidth = 1 * this.viewport.scale
 
     return {
-      top: initRect.top - actualbdwidth,
-      left: initRect.left - actualbdwidth,
-      width: initRect.width + actualbdwidth * 2,
-      height: initRect.height + actualbdwidth * 2,
+      top: initRect.top,
+      left: initRect.left,
+      width: initRect.width,
+      height: initRect.height,
     }
   }
 }

+ 64 - 361
packages/core/src/annotation/redaction.js

@@ -34,18 +34,15 @@ export default class Redaction extends Base {
     this.start = null
     this.end = null
 
-    this.newStart = null
-    this.newEnd = null
-
     this.startCircle = null
     this.endCircle = null
 
     this.show = show
     this.highlight = highlight
 
+
     this.onMousedown = this.handleMouseDown.bind(this)
     this.onMouseup = this.handleMouseUp.bind(this)
-    this.onMousemove = this.handleMouseMove.bind(this)
     this.onDelete = this.handleDelete.bind(this)
     this.render()
   }
@@ -59,6 +56,7 @@ export default class Redaction extends Base {
     )
     this.start = start
     this.end = end
+    this.isRedaction = this.annotation.type === 'redaction'
 
     const rect = this.calculate(start, end)
     
@@ -69,45 +67,58 @@ export default class Redaction extends Base {
     annotationContainer.style.left = rect.left + 'px'
     annotationContainer.style.width = rect.width + 'px'
     annotationContainer.style.height = rect.height + 'px'
-    annotationContainer.style.backgroundColor = this.highlight ? '#93B9FD' : ''
-    annotationContainer.style.opacity = 0.3
     this.annotationContainer = annotationContainer
-    this.annotationContainer.addEventListener('mousedown', this.handleClick.bind(this))
 
+    const actualbdwidth = (annotation.borderWidth || 2) * this.scale
     let shapeElement
-    shapeElement = createElement(
-      'div',
-      {
-        left: 0,
-        top: 0,
-        width: `${Math.abs(rect.width)}px`,
-        height: `${Math.abs(rect.height)}px`,
-        position: 'relative'
-      }
-    )
-
+    if (this.isRedaction) {
+      shapeElement = createElement(
+        'div',
+        {
+          left: 0,
+          top: 0,
+          width: `${Math.abs(rect.width)}px`,
+          height: `${Math.abs(rect.height)}px`,
+          position: 'relative',
+          border: `${actualbdwidth}px solid #FF0000`,
+          transition: 'none'
+        }
+      )
+
+      this.annotationContainer.addEventListener('mousedown', this.handleClick.bind(this))
+      this.annotationContainer.addEventListener('mouseenter', () => {
+        shapeElement.style.backgroundColor = '#000000'
+        shapeElement.style.border = ''
+      })
+      this.annotationContainer.addEventListener('mouseleave', () => {
+        shapeElement.style.backgroundColor = ''
+        shapeElement.style.border = `${actualbdwidth}px solid #FF0000`
+      })
+    } else {
+      shapeElement = document.createElementNS("http://www.w3.org/2000/svg", "svg")
+      shapeElement.setAttribute("viewBox", `0 0 ${rect.width} ${rect.height}`)
+      shapeElement.addEventListener('mousedown', this.handleClick.bind(this))
+
+      const rectElement = createSvg(
+        "rect",
+        {
+          x: actualbdwidth / 2,
+          y: actualbdwidth / 2,
+          width: `${Math.abs(rect.width - actualbdwidth)}px`,
+          height: `${Math.abs(rect.height - actualbdwidth)}px`,
+          stroke: annotation.borderColor || '#FF0000',
+          'stroke-width': actualbdwidth,
+          'stroke-opacity': annotation.opacity || 1,
+          fill: annotation.fillColor ? annotation.fillColor : 'none',
+          'fill-opacity': annotation.opacity || 1,
+        }
+      )
+      shapeElement.append(rectElement)
+    }
     this.shapeElement = shapeElement
-
     this.annotationContainer.append(this.shapeElement)
-
     this.container.append(this.annotationContainer)
 
-    this.buttonContainer = createElement(
-      'a',
-      {
-        display: 'block',
-        width: '100%',
-        height: '100%',
-        outline: 'none'
-      }
-    )
-    this.shapeElement.append(this.buttonContainer)
-    this.buttonContainer.addEventListener('blur', this.onBlur)
-    if (this.annotation.url) {
-      this.buttonContainer.setAttribute('href', this.annotation.url)
-      this.buttonContainer.setAttribute('target', '_blank')
-    }
-
     this.outerLineContainer = document.createElement('div')
     this.outerLineContainer.className = 'outline-container'
     this.deletetButton = createSvg(
@@ -154,140 +165,24 @@ export default class Redaction extends Base {
         height: rect.height + 8,
         x: 4,
         y: 4
-      }
-    );
-    
-    this.topLeftRect = createSvg(
-      "rect",
-      {
-        class: "nw-resize",
-        'data-id': "nw-resize",
-        fill: "#FFFFFF",
-        stroke: "#4982E6",
-        'stroke-width': 1,
-        width: 6,
-        height: 6,
-        x: 1,
-        y: 1
-      }
-    );
-
-    this.LeftRect = createSvg(
-      "rect",
-      {
-        class: "w-resize",
-        'data-id': "w-resize",
-        fill: "#FFFFFF",
-        stroke: "#4982E6",
-        'stroke-width': 1,
-        width: 6,
-        height: 6,
-        x: 1,
-        y: rect.height / 2 + 5
-      }
-    );
-
-    this.bottomLeftRect = createSvg(
-      "rect",
-      {
-        class: "sw-resize",
-        'data-id': "sw-resize",
-        fill: "#FFFFFF",
-        stroke: "#4982E6",
-        'stroke-width': 1,
-        width: 6,
-        height: 6,
-        x: 1,
-        y: rect.height + 9
-      }
-    );
-
-    this.bottomRect = createSvg(
-      "rect",
-      {
-        class: "s-resize",
-        'data-id': "s-resize",
-        fill: "#FFFFFF",
-        stroke: "#4982E6",
-        'stroke-width': 1,
-        width: 6,
-        height: 6,
-        x: rect.width / 2 + 5,
-        y: rect.height + 9
-      }
-    );
-    
-    this.bottomRightRect = createSvg(
-      "rect",
-      {
-        class: "se-resize",
-        'data-id': "se-resize",
-        fill: "#FFFFFF",
-        stroke: "#4982E6",
-        'stroke-width': 1,
-        width: 6,
-        height: 6,
-        x: rect.width + 9,
-        y: rect.height + 9
-      }
-    );
-
-    this.rightRect = createSvg(
-      "rect",
-      {
-        class: "e-resize",
-        'data-id': "e-resize",
-        fill: "#FFFFFF",
-        stroke: "#4982E6",
-        'stroke-width': 1,
-        width: 6,
-        height: 6,
-        x: rect.width + 9,
-        y: rect.height / 2 + 5
-      }
-    );
-
-    this.topRightRect = createSvg(
-      "rect",
-      {
-        class: "ne-resize",
-        'data-id': "ne-resize",
-        fill: "#FFFFFF",
-        stroke: "#4982E6",
-        'stroke-width': 1,
-        width: 6,
-        height: 6,
-        x: rect.width + 9,
-        y: 1
-      }
-    );
-
-    this.topRect = createSvg(
-      "rect",
-      {
-        class: "n-resize",
-        'data-id': "n-resize",
-        fill: "#FFFFFF",
-        stroke: "#4982E6",
-        'stroke-width': 1,
-        width: 6,
-        height: 6,
-        x: rect.width / 2 + 5,
-        y: 1
+      }, {
+        cursor: 'pointer'
       }
     );
 
     this.outerLine.append(this.moveRect)
-    this.outerLine.append(this.topLeftRect)
-    this.outerLine.append(this.LeftRect)
-    this.outerLine.append(this.bottomLeftRect)
-    this.outerLine.append(this.bottomRect)
-    this.outerLine.append(this.bottomRightRect)
-    this.outerLine.append(this.rightRect)
-    this.outerLine.append(this.topRightRect)
-    this.outerLine.append(this.topRect)
     this.outerLineContainer.append(this.outerLine)
     this.outerLineContainer.append(this.deletetButton)
+
+    this.isRedaction && this.moveRect.addEventListener('mouseenter', () => {
+      this.shapeElement.style.backgroundColor = '#000000'
+      this.shapeElement.style.border = ''
+    })
+    this.isRedaction && this.moveRect.addEventListener('mouseleave', () => {
+      this.shapeElement.style.backgroundColor = ''
+      this.shapeElement.style.border = '2px solid #FF0000'
+    })
+
     this.initial = true
   }
 
@@ -328,13 +223,12 @@ export default class Redaction extends Base {
 
   calculate (start, end) {
     const initRect = this.rectCalc({ start, end });
-    const actualbdwidth = (this.annotation.borderWidth || 2) * this.scale
 
     return {
-      top: initRect.top - actualbdwidth,
-      left: initRect.left - actualbdwidth,
-      width: initRect.width + actualbdwidth * 2,
-      height: initRect.height + actualbdwidth * 2,
+      top: initRect.top,
+      left: initRect.left,
+      width: initRect.width,
+      height: initRect.height,
     }
   }
 
@@ -381,7 +275,7 @@ export default class Redaction extends Base {
 
   handleClick () {
     if (!this.hidden || document.fullscreenElement || this.layer.annotationStore.creating || this.layer.toolMode === 'editor') return
-    if (this.layer.toolMode === 'security' && this.layer.tool === 'redaction') {
+    if (this.layer.toolMode === 'security') {
       this.hidden = false
       this.updateTool()
 
@@ -418,146 +312,12 @@ export default class Redaction extends Base {
       clickY: pageY,
     }
 
-    document.addEventListener('mousemove', this.onMousemove)
     document.addEventListener('mouseup', this.onMouseup)
-    document.addEventListener('touchmove', this.onMousemove)
     document.addEventListener('touchend', this.onMouseup)
 
     this.handleClick()
   };
 
-  handleMouseMove (event) {
-    if (event.button !== 0 && event.type === 'mousemove') return
-    if (event.type === 'touchmove') {
-      document.querySelector('.document-container').style.overflow = 'hidden'
-    }
-    this.moving = true
-    const { pageX, pageY } = getClickPoint(event)
-    const start = this.start
-    const end = this.end
-    const startState = this.startState
-
-    const { width, height } = this.viewport
-    if (startState.operator === 'nw-resize') {
-      let x1 = pageX - (startState.clickX - this.start.x);
-      let y1 = pageY - (startState.clickY - this.start.y);
-      x1 = Math.min(end.x - 5, x1)
-      x1 = Math.max(0, x1)
-      y1 = Math.min(end.y - 5, y1)
-      y1 = Math.max(0, y1)
-
-      this.update({
-        start: { x: x1, y: y1 },
-        end,
-      });
-    } else if (startState.operator === 'w-resize') {
-      let x1 = pageX - (startState.clickX - this.start.x);
-      x1 = Math.min(end.x - 5, x1)
-      x1 = Math.max(0, x1)
-
-      this.update({
-        start: { x: x1, y: start.y },
-        end,
-      });
-    } else if (startState.operator === 'sw-resize') {
-      let x1 = pageX - (startState.clickX - this.start.x);
-      let y1 = pageY - (startState.clickY - this.end.y);
-      x1 = Math.min(end.x - 5, x1)
-      x1 = Math.max(0, x1)
-      y1 = Math.min(height, y1)
-      y1 = Math.max(start.y + 5, y1)
-
-      this.update({
-        start: { x: x1, y: start.y },
-        end: { x: end.x, y: y1 },
-      });
-    } else if (startState.operator === 's-resize') {
-      let y1 = pageY - (startState.clickY - this.end.y);
-      y1 = Math.min(height, y1)
-      y1 = Math.max(start.y + 5, y1)
-      this.update({
-        start,
-        end: { x: end.x, y: y1 },
-      });
-    } else if (startState.operator === 'se-resize') {
-      let x = pageX - (startState.clickX - this.end.x);
-      let y = pageY - (startState.clickY - this.end.y);
-      x = Math.min(width, x)
-      x = Math.max(start.x + 5, x)
-      y = Math.min(height, y)
-      y = Math.max(start.y + 5, y)
-      this.update({
-        start,
-        end: { x, y },
-      });
-    } else if (startState.operator === 'e-resize') {
-      let x = pageX - (startState.clickX - this.end.x);
-      x = Math.min(width, x)
-      x = Math.max(start.x + 5, x)
-      this.update({
-        start,
-        end: { x, y: end.y },
-      });
-    } else if (startState.operator === 'ne-resize') {
-      let x = pageX - (startState.clickX - this.end.x);
-      let y = pageY - (startState.clickY - this.start.y);
-      x = Math.min(width, x)
-      x = Math.max(start.x + 5, x)
-      y = Math.min(end.y - 5, y)
-      y = Math.max(0, y)
-      this.update({
-        start: { x: start.x, y },
-        end: { x, y: end.y },
-      });
-    } else if (startState.operator === 'n-resize') {
-      let y = pageY - (startState.clickY - this.start.y);
-      y = Math.min(end.y - 5, y)
-      y = Math.max(0, y)
-      this.update({
-        start: { x: start.x, y },
-        end,
-      });
-    } else if (startState.operator === 'move') {
-      let x1 = pageX - (startState.clickX - this.start.x);
-      let y1 = pageY - (startState.clickY - this.start.y);
-      let x2 = pageX - (startState.clickX - this.end.x);
-      let y2 = pageY - (startState.clickY - this.end.y);
-
-      const rect = {
-        width: Math.abs(start.x -end.x),
-        height: Math.abs(start.y -end.y)
-      }
-      if (x1 < x2) {
-        x1 = Math.max(0, x1)
-        x1 = Math.min(width - rect.width, x1)
-        x2 = Math.max(rect.width, x2)
-        x2 = Math.min(width, x2)
-      } else {
-        x2 = Math.max(0, x2)
-        x2 = Math.min(width - rect.width, x2)
-        x1 = Math.max(rect.width, x1)
-        x1 = Math.min(width, x1)
-      }
-
-      if (y1 < y2) {
-        y1 = Math.max(0, y1)
-        y1 = Math.min(height - rect.height, y1)
-        y2 = Math.max(rect.height, y2)
-        y2 = Math.min(height, y2)
-      } else {
-        y2 = Math.max(0, y2)
-        y2 = Math.min(height - rect.height, y2)
-        y1 = Math.max(rect.height, y1)
-        y1 = Math.min(height, y1)
-      }
-
-      this.update({
-        start: { x: x1, y: y1 },
-        end: { x: x2, y: y2 },
-      });
-    }
-  }
-
   handleDelete (event) {
     if (!this.annotationContainer) return
     if (this.layer.tool && event) {
@@ -590,18 +350,12 @@ export default class Redaction extends Base {
     if (event.type === 'touchend') {
       document.querySelector('.document-container').style.overflow = 'auto'
     }
-    this.moving = false
-    document.removeEventListener('mousemove', this.onMousemove)
     document.removeEventListener('mouseup', this.onMouseup)
-    document.removeEventListener('touchmove', this.onMousemove)
     document.removeEventListener('touchend', this.onMouseup)
     
     const { pageX, pageY } = getClickPoint(event)
     if (pageX === this.startState.clickX && pageY === this.startState.clickY) return
 
-    this.start = this.newStart
-    this.end = this.newEnd
-
     const annotation = this.annotation
     const { start, end } = this.getInitialPoint()
 
@@ -624,55 +378,4 @@ export default class Redaction extends Base {
       }
     })
   }
-
-  update ({ start, end }) {
-    const rect = this.calculate(start, end)
-
-    this.newStart = start
-    this.newEnd = end
-
-    this.annotationContainer.style.top = rect.top + 'px'
-    this.annotationContainer.style.left = rect.left + 'px'
-    this.annotationContainer.style.width = rect.width + 'px'
-    this.annotationContainer.style.height = rect.height + 'px'
-    
-    this.annotationContainer.style.width = `${rect.width}px`;
-    this.annotationContainer.style.height = `${rect.height}px`;
-
-    this.shapeElement.style.width = `${rect.width}px`;
-    this.shapeElement.style.height = `${rect.height}px`;
-
-    this.outerLine.style.left = `${rect.left - 8}px`
-    this.outerLine.style.top = `${rect.top - 8}px`
-    this.outerLine.style.width = `${rect.width + 8 * 2}px`
-    this.outerLine.style.height = `${rect.height + 8 * 2}px`
-
-    const left = (rect.left + rect.width - 30) + 'px'
-    const top = (rect.top + rect.height + 8) + 'px'
-    this.deletetButton.style.left = left
-    this.deletetButton.style.top = top
-    
-    this.moveRect.setAttribute('width',  rect.width + 9)
-    this.moveRect.setAttribute('height',  rect.height + 9)
-    
-    this.LeftRect.setAttribute("y", rect.height / 2 + 5)
-
-    this.bottomLeftRect.setAttribute("y", rect.height + 9)
-
-    this.bottomRect.setAttribute("x", rect.width / 2 + 5)
-    this.bottomRect.setAttribute("y", rect.height + 9)
-
-
-    this.bottomRightRect.setAttribute("x", rect.width + 9)
-    this.bottomRightRect.setAttribute("y", rect.height + 9)
-
-
-    this.rightRect.setAttribute("x", rect.width + 9)
-    this.rightRect.setAttribute("y", rect.height / 2 + 5)
-
-    
-    this.topRightRect.setAttribute("x", rect.width + 9)
-
-    this.topRect.setAttribute("x", rect.width / 2 + 5)
-  }
 }

+ 4 - 2
packages/core/src/form/signature_fields.js

@@ -768,9 +768,11 @@ export default class SignatureFields extends Base {
       imgEle.style.height = '100%'
       imgEle.style.pointerEvents = 'none'
 
-      if (this.isDigital) this.annotationContainer.style.zIndex = 1
+      if (this.isDigital) {
+        this.annotationContainer.style.zIndex = 1
+        this.shapeElement.style.pointerEvents = 'none'
+      }
       this.shapeElement.style.position = 'absolute'
-      this.shapeElement.style.zIndex = -1
 
       imgEle.src = imgSrc
       this.imgEle = imgEle

+ 21 - 0
packages/core/src/index.js

@@ -91,6 +91,7 @@ class ComPDFKitViewer {
     this.annotator = 'Guest'
     this.isAnnotationModified = false
     this.isContentEditModyfied = false
+    this.redactionList = []
   }
 
   get pagesCount() {
@@ -1701,6 +1702,8 @@ class ComPDFKitViewer {
           currentAnnotation = this.handleInk(currentAnnotation)
         }
         annotateHandles.push(currentAnnotation)
+        if (annotation[i].type === 'redaction' || annotation[i].type === 'remove') this.redactionList.push(annotation[i])
+        console.log(annotation, this.redactionList)
       }
     } else {
       const annotations = this.annotations[annotation.pageIndex]
@@ -1734,11 +1737,13 @@ class ComPDFKitViewer {
         if (!annotations.length) {
           delete this.annotations[annotation.pageIndex]
         }
+        this.redactionList = this.redactionList.filter((item) => item.name !== annotation.name)
       } else if (data.type === 'empty') {
         annotations.splice(index, 1)
         if (!annotations.length) {
           delete this.annotations[annotation.pageIndex]
         }
+        this.redactionList = this.redactionList.filter((item) => item.pageIndex !== annotation.pageIndex)
         return
       } else {
         if (!this.webviewerServer) {
@@ -1775,6 +1780,9 @@ class ComPDFKitViewer {
       await this.#postAnnotations(annotateHandles)
     }
 
+    console.log(this.redactionList)
+    this.eventBus.dispatch('showRedactionApplyBar', this.redactionList.length ? true : false)
+
     this.isAnnotationModified = true
     return true
   }
@@ -2864,6 +2872,12 @@ class ComPDFKitViewer {
       await this.updateTextPtr();
     }
 
+    if (oldMode === 'security' && this.toolMode !== 'security') {
+      console.log('delete: ', this.redactionList)
+      this.delAnnotations(this.redactionList)
+      this.pdfViewer.refresh()
+    }
+
     for (let page in this.annotations) {
       for (let i = 0; i < this.annotations[page].length; i++) {
         const annot = this.annotations[page][i]
@@ -4287,6 +4301,13 @@ class ComPDFKitViewer {
   setContentEditModyfied(value) {
     this.isContentEditModyfied = value
   }
+
+  applyRedactions(redaction) {
+    console.log('apply: ', this.redactionList)
+    const redactions = Array.isArray(redaction) ? redaction : [redaction]
+    // redactions.length > 0 && this.addAnnotations(redactions)
+    this.redactionList = []
+  }
 }
 
 class PDFWorker {

+ 9 - 1
packages/webview/src/components/Dialogs/Dialog.vue

@@ -1,7 +1,7 @@
 <template>
   <div v-if="show" class="dialog">
     <div class="dialog-container">
-      <div class="close" v-if="close" @click="typeof close === 'function'  ? close($event) : closeDialog">
+      <div class="close" v-if="close" @click="handleClose">
         <CloseA v-if="close === 'A'" />
         <CloseB v-if="close === 'B'" />
         <Close v-else />
@@ -32,6 +32,14 @@ const useViewer = useViewerStore()
 const closeDialog = () => {
   useViewer.closeElement(dialogName)
 }
+
+const handleClose = (event) => {
+  if (typeof close === 'function') {
+    close(event)
+  } else {
+    closeDialog()
+  }
+}
 </script>
 
 <style lang="scss">

+ 2 - 1
packages/webview/src/components/DocumentContainer/DocumentContainer.vue

@@ -62,6 +62,7 @@
   <CertificationViewerDialog />
   <DeleteSignatureDialog />
   <AddDigitalFileDialog />
+  <RedactionConfirmDialog />
   <div v-if="loading && loadingPercent < 100" class="loading-state">{{ $t('loading') }}...</div>
   <div v-show="!load && activePanelTab !== 'COMPARISON' && ['compare', 'view'].includes(toolMode)" class="upload-container">
     <input id="fileInput" type="file" accept=".pdf" @change="handleUpload" name="document"/>
@@ -423,7 +424,7 @@ window.instances.UI.loadDocument = async (file, {
   z-index: 2;
 }
 
-.annotationLayer svg {
+.annotationLayer > svg {
   display: block;
   pointer-events: auto;
 }

+ 2 - 2
packages/webview/src/components/Header/Header.vue

@@ -4,7 +4,7 @@
   </div>
   <Toolbar />
   <SignatureVerifyBar v-if="signatureVerify && toolMode === 'sign'" />
-  <SecurityApplyBar v-if="toolMode === 'security' && activeTool === 'redaction'" />
+  <SecurityApplyBar v-show="toolMode === 'security' && showRedactionApplyBar" />
 </template>
 
 <script setup>
@@ -20,7 +20,7 @@ const rightItems = useViewer.getActiveRightHeaderItems
 const toolMode = computed(() => useViewer.getToolMode)
 const signatureVerify = computed(() => useViewer.getSignatureVerify)
 const isDisabledHeader = computed(() => useViewer.getDisabledHeader)
-const activeTool = computed(() => useDocument.getActiveTool)
+const showRedactionApplyBar = computed(() => useViewer.getShowRedactionApplyBar)
 </script>
 
 <style lang="scss">

+ 4 - 0
packages/webview/src/components/HeaderItems/HeaderItems.vue

@@ -173,6 +173,10 @@ const changeToolMode = (mode) => {
     else useViewer.openElement('docEditorPreventDialog')
     return
   }
+  if (prop.toolMode !== mode && prop.toolMode === 'security' && (activeTool.value === 'redaction' || activeTool.value === 'remove')) {
+    useViewer.setRedactionConfirmDialog(useViewer.getShowRedactionApplyBar ? 1 : 0)
+    return
+  }
   if (mode === 'sign') {
     window.$message.info($t('signatures.tip'), {
       duration: 3000,

+ 72 - 0
packages/webview/src/components/SecurityToolbar/RedactionConfirmDialog.vue

@@ -0,0 +1,72 @@
+<template>
+  <div class="redaction-confirm-dialog" v-if="show">
+    <Dialog :show="show" :close="closeDialog" class="confirm-dialog">
+      <template #header>
+        <p>{{ dialogType === 1 ? $t('security.saveAs') : $t('security.note') }}</p>
+      </template>
+
+      <template v-if="dialogType === 1">
+        <p>{{ $t('security.saveTip[0]') }}</p><br />
+        <p>{{ $t('security.saveTip[1]') }}</p>
+      </template>
+      <p v-else>{{ $t('security.exitTip') }}</p>
+
+      <template #footer>
+        <div class="rect-button white" @click="closeDialog">{{ $t('cancel') }}</div>
+        <div class="rect-button blue" @click="confirm">{{ dialogType === 1 ? $t('security.saveAsConfirm') : $t('security.exitConfirm') }}</div>
+      </template>
+    </Dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue'
+import { useViewerStore } from '@/stores/modules/viewer'
+import { useDocumentStore } from '@/stores/modules/document'
+import core from '@/core'
+
+const useViewer = useViewerStore()
+const useDocument = useDocumentStore()
+
+const dialogType = computed(() => useViewer.getRedactionConfirmDialog)
+const show = computed(() => dialogType.value >= 0)
+
+const closeDialog = () => {
+  useViewer.setRedactionConfirmDialog(-1)
+}
+
+const confirm = () => {
+  dialogType.value === 1 && core.applyRedactions()
+  useDocument.setToolState('')
+  useViewer.setActiveToolMode('view')
+  useViewer.setShowRedactionApplyBar(false)
+  closeDialog()
+}
+</script>
+
+<style lang="scss">
+.redaction-confirm-dialog {
+  .dialog-container {
+    max-width: 381px;
+
+    header p {
+      font-size: 18px;
+    }
+
+    main {
+      margin-top: 16px;
+      margin-bottom: 20px;
+
+      p {
+        font-size: 14px;
+        line-height: 16px;
+        font-weight: 400;
+      }
+    }
+
+    footer .rect-button.blue {
+      border-radius: 4px;
+    }
+  }
+}
+</style>

+ 13 - 62
packages/webview/src/components/SecurityToolbar/SecurityApplyBar.vue

@@ -5,28 +5,11 @@
       <div class="view-button" @click="showDialog(0)">{{ $t('security.exit') }}</div>
       <div class="view-button" @click="showDialog(1)">{{ $t('security.apply') }}</div>
     </div>
-
-    <Dialog :show="showConfirmDialog" class="confirm-dialog">
-      <template #header>
-        <p>{{ comfirmType ? $t('security.saveAs') : $t('security.note') }}</p>
-      </template>
-
-      <template v-if="comfirmType">
-        <p>{{ $t('security.saveTip[0]') }}</p><br />
-        <p>{{ $t('security.saveTip[1]') }}</p>
-      </template>
-      <p v-else>{{ $t('security.exitTip') }}</p>
-
-      <template #footer>
-        <div class="rect-button white" @click="showConfirmDialog = false">{{ $t('cancel') }}</div>
-        <div class="rect-button blue" @click="confirm">{{ comfirmType ? $t('security.saveAsConfirm') : $t('security.exitConfirm') }}</div>
-      </template>
-    </Dialog>
   </div>
 </template>
 
 <script setup>
-import { computed, ref } from 'vue'
+import { ref, computed } from 'vue'
 import { useViewerStore } from '@/stores/modules/viewer'
 import { useDocumentStore } from '@/stores/modules/document'
 import core from '@/core'
@@ -34,25 +17,18 @@ import core from '@/core'
 const useViewer = useViewerStore()
 const useDocument = useDocumentStore()
 
-const signatureVerify = computed(() => useViewer.getSignatureVerify)
-
-const showConfirmDialog = ref(false)
-const comfirmType = ref(0) // 0: exit, 1: apply
+const showRedactionApplyBar = computed(() => useViewer.getShowRedactionApplyBar)
 
 const showDialog = (type) => {
-  showConfirmDialog.value = true
-  comfirmType.value = type
+  useViewer.setRedactionConfirmDialog(type)
 }
 
-const confirm = () => {
-  if (comfirmType.value) {
-    // core.applyRedactions()
-  } else {
-    useDocument.setToolState('')
-    useViewer.setActiveToolMode('view')
+const showBar = (value) => {
+  if (showRedactionApplyBar.value !== value) {
+    useViewer.setShowRedactionApplyBar(value)
   }
-  showConfirmDialog.value = false
 }
+core.addEvent('showRedactionApplyBar', showBar)
 </script>
 
 <style lang="scss">
@@ -65,7 +41,12 @@ const confirm = () => {
   z-index: 4;
   min-height: 48px;
   background: #DDE9FF;
-  animation: ease-out 1s;
+
+  &.hidden {
+    visibility: hidden;
+    pointer-events: none;
+    z-index: -1;
+  }
 
   span {
     font-size: 14px;
@@ -99,36 +80,6 @@ const confirm = () => {
       }
     }
   }
-
-  .confirm-dialog {
-    .dialog-container {
-      max-width: 381px;
-
-      header p {
-        font-size: 18px;
-      }
-
-      main {
-        margin-top: 16px;
-        margin-bottom: 20px;
-
-        p {
-          font-size: 14px;
-          line-height: 16px;
-          font-weight: 400;
-        }
-      }
-
-      footer .rect-button.blue {
-        border-radius: 4px;
-      }
-    }
-  }
-}
-
-@keyframes ease-out {
-  from { opacity: 0; }
-  to { opacity: 1; }
 }
 
 @media screen and (max-width: 500px) {

+ 9 - 7
packages/webview/src/components/SecurityToolbar/SecurityToolbar.vue

@@ -33,14 +33,14 @@
       class="security-popover"
     >
       <template #trigger>
-        <Button :class="{ active: showRedactionPopover || activeTool === 'redaction' }">
-          <span>{{ $t('security.redaction') }}</span>
+        <Button :class="{ active: showRedactionPopover || activeTool === 'redaction' || activeTool === 'remove' }">
+          <span>{{ activeTool === 'remove' ? $t('security.remove') : $t('security.redaction') }}</span>
           <Arrow class="arrow-left" />
         </Button>
       </template>
       <div class="drop-down">
-        <div class="drop-item" @click="redaction">{{ $t('security.redaction') }}</div>
-        <div class="drop-item" @click="remove">{{ $t('security.remove') }}</div>
+        <div class="drop-item" @click="handleTool('redaction')">{{ $t('security.redaction') }}</div>
+        <div class="drop-item" @click="handleTool('remove')">{{ $t('security.remove') }}</div>
       </div>
     </n-popover>
 
@@ -82,6 +82,7 @@ const redactionPrompted = computed(() => useDocument.getRedactionPrompted)
 const securityPopover = ref()
 const redactionPopover = ref()
 const showTipDialog = ref(false)
+let firstEnterTool = ''
 
 const changeActiveTool = (tool) => {
   useDocument.setToolState(tool)
@@ -117,11 +118,12 @@ const removePassword = async () => {
   loading.value = false
 }
 
-const redaction = () => {
+const handleTool = (tool) => {
   if (!redactionPrompted.value) {
+    firstEnterTool = tool
     showTipDialog.value = true
   } else {
-    useDocument.setToolState('redaction')
+    useDocument.setToolState(tool)
   }
   redactionPopover.value.setShow(false)
 }
@@ -129,7 +131,7 @@ const redaction = () => {
 const closeDialog = () => {
   showTipDialog.value = false
   useDocument.setRedactionPrompted(true)
-  useDocument.setToolState('redaction')
+  useDocument.setToolState(firstEnterTool)
 }
 </script>
 

+ 3 - 0
packages/webview/src/core/applyRedactions.js

@@ -0,0 +1,3 @@
+import core from '@/core'
+
+export default () => core.getDocumentViewer().applyRedactions()

+ 2 - 0
packages/webview/src/core/index.js

@@ -77,6 +77,7 @@ import resetOperate from './resetOperate'
 import eventBus from './eventBus'
 import handleContentEditorPopup from './handleContentEditorPopup'
 import addAnnotationImage from './addAnnotationImage'
+import applyRedactions from './applyRedactions'
 
 export default {
   getDocumentViewer,
@@ -161,4 +162,5 @@ export default {
   eventBus,
   handleContentEditorPopup,
   addAnnotationImage,
+  applyRedactions
 }

+ 14 - 0
packages/webview/src/stores/modules/viewer.js

@@ -434,6 +434,8 @@ export const useViewerStore = defineStore({
     ],
     isDisabledHeader: false,
     rightPanelButtonDisabled: true,
+    showRedactionApplyBar: false,
+    redactionConfirmDialog: -1, // 0: exit, 1: apply
   }),
   getters: {
     getLanguage () {
@@ -559,6 +561,12 @@ export const useViewerStore = defineStore({
     getCustomFonts () {
       return this.customFonts
     },
+    getShowRedactionApplyBar () {
+      return this.showRedactionApplyBar
+    },
+    getRedactionConfirmDialog () {
+      return this.redactionConfirmDialog
+    },
   },
   actions: {
     resetSetting () {
@@ -783,5 +791,11 @@ export const useViewerStore = defineStore({
         }
       }
     },
+    setShowRedactionApplyBar (val) {
+      this.showRedactionApplyBar = val
+    },
+    setRedactionConfirmDialog (val) {
+      this.redactionConfirmDialog = val
+    },
   }
 })