Browse Source

添加自定义按钮;二级菜单栏annotation和form的功能按钮支持显示隐藏

wzl 1 year ago
parent
commit
6a6b71af84

+ 1 - 1
packages/webview/src/apis/disableElements.js

@@ -1,6 +1,6 @@
 // 隐藏工具栏按钮
 export default (store) => (dataElements) => {
-  const headerItems = [...store.getActiveHeaderItems, ...store.getTools, ...store.getActiveRightHeaderItems];
+  const headerItems = [...store.getActiveHeaderItems, ...store.getTools, ...store.getActiveRightHeaderItems, ...store.getToolItems.annotation[0].tools, ...store.getToolItems.form];
 
   if (Array.isArray(dataElements)) {
     for (let i = 0; i < headerItems.length; i++) {

+ 1 - 1
packages/webview/src/apis/enableElements.js

@@ -1,6 +1,6 @@
 // 显示工具栏按钮
 export default (store) => (dataElements) => {
-  const headerItems = [...store.getActiveHeaderItems, ...store.getTools, ...store.getActiveRightHeaderItems];
+  const headerItems = [...store.getActiveHeaderItems, ...store.getTools, ...store.getActiveRightHeaderItems, ...store.getToolItems.annotation[0].tools, ...store.getToolItems.form];
 
   if (Array.isArray(dataElements)) {
     for (let i = 0; i < headerItems.length; i++) {

+ 34 - 32
packages/webview/src/components/Annotate/Annotate.vue

@@ -1,40 +1,42 @@
 <template>
-  <StickyNoteButton />
-  <div class="markup-container">
-    <Button :class="{ active: markupActive && markupTool === 'highlight' }" @click.stop="changeMarkupTool('highlight')" :title="$t('header.highlight')"><Highlight /></Button>
-    <Button :class="{ active: markupActive && markupTool === 'underline' }" @click.stop="changeMarkupTool('underline')" :title="$t('header.underline')"><Underline /></Button>
-    <Button :class="{ active: markupActive && markupTool === 'strikeout' }" @click.stop="changeMarkupTool('strikeout')" :title="$t('header.strikeout')"><Strikeout /></Button>
-    <Button :class="{ active: markupActive && markupTool === 'squiggly' }" @click.stop="changeMarkupTool('squiggly')" :title="$t('header.squiggly')"><Squiggle /></Button>
-  </div>
-  <Button :class="{ active: activeTool === 'ink' }" @click="changeActiveTool('ink')" :title="$t('header.ink')">
-    <Ink />
-  </Button>
-  <div class="shape-container">
-    <Button :class="{ active: shapeActive && shapeTool === 'circle' }" @click.stop="changeShapeTool('circle')" :title="$t('header.circle')">
-      <EllipseTool />
+  <template v-for="(tool, index) in item" :key="`${tool.type}-${tool.dataElement || index}`">
+    <StickyNoteButton v-if="tool.type === 'note' && !tool.hidden" :data-element="tool.dataElement" />
+    <div v-else-if="['highlight', 'underline', 'strikeout', 'squiggly'].includes(tool.type)" class="markup-container">
+      <Button v-if="tool.type === 'highlight' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: markupActive && markupTool === 'highlight' }" @click.stop="changeMarkupTool('highlight')" :title="$t('header.highlight')"><Highlight /></Button>
+      <Button v-if="tool.type === 'underline' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: markupActive && markupTool === 'underline' }" @click.stop="changeMarkupTool('underline')" :title="$t('header.underline')"><Underline /></Button>
+      <Button v-if="tool.type === 'strikeout' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: markupActive && markupTool === 'strikeout' }" @click.stop="changeMarkupTool('strikeout')" :title="$t('header.strikeout')"><Strikeout /></Button>
+      <Button v-if="tool.type === 'squiggly' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: markupActive && markupTool === 'squiggly' }" @click.stop="changeMarkupTool('squiggly')" :title="$t('header.squiggly')"><Squiggle /></Button>
+    </div>
+    <Button v-if="tool.type === 'note' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: activeTool === 'ink' }" @click="changeActiveTool('ink')" :title="$t('header.ink')">
+      <Ink />
     </Button>
-    <Button :class="{ active: shapeActive && shapeTool === 'square' }" @click.stop="changeShapeTool('square')" :title="$t('header.square')">
-      <RectangleTool />
+    <div v-else-if="['circle', 'square', 'arrow', 'line'].includes(tool.type)" class="shape-container">
+      <Button v-if="tool.type === 'circle' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: shapeActive && shapeTool === 'circle' }" @click.stop="changeShapeTool('circle')" :title="$t('header.circle')">
+        <EllipseTool />
+      </Button>
+      <Button v-if="tool.type === 'square' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: shapeActive && shapeTool === 'square' }" @click.stop="changeShapeTool('square')" :title="$t('header.square')">
+        <RectangleTool />
+      </Button>
+      <Button v-if="tool.type === 'arrow' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: shapeActive && shapeTool === 'arrow' }" @click.stop="changeShapeTool('arrow')" :title="$t('header.arrow')">
+        <ArrowTool />
+      </Button>
+      <Button v-if="tool.type === 'line' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: shapeActive && shapeTool === 'line' }" @click.stop="changeShapeTool('line')" :title="$t('header.line')">
+        <LineTool />
+      </Button>
+    </div>
+    <Button v-else-if="tool.type === 'freetext' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: activeTool === 'freetext' }" @click="changeActiveTool('freetext')" :title="$t('header.freetext')">
+      <Text />
     </Button>
-    <Button :class="{ active: shapeActive && shapeTool === 'arrow' }" @click.stop="changeShapeTool('arrow')" :title="$t('header.arrow')">
-      <ArrowTool />
+    <Button v-else-if="tool.type === 'stamp' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: activeTool === 'stamp' }" @click="changeActiveTool('stamp')" :title="$t('header.stamp')">
+      <Stamp />
     </Button>
-    <Button :class="{ active: shapeActive && shapeTool === 'line' }" @click.stop="changeShapeTool('line')" :title="$t('header.line')">
-      <LineTool />
+    <Button v-else-if="tool.type === 'image' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: activeTool === 'image' }" @click="changeActiveTool('image')" :title="$t('header.image')">
+      <Image />
     </Button>
-  </div>
-  <Button :class="{ active: activeTool === 'freetext' }" @click="changeActiveTool('freetext')" :title="$t('header.freetext')">
-    <Text />
-  </Button>
-  <Button :class="{ active: activeTool === 'stamp' }" @click="changeActiveTool('stamp')" :title="$t('header.stamp')">
-    <Stamp />
-  </Button>
-  <Button :class="{ active: activeTool === 'image' }" @click="changeActiveTool('image')" :title="$t('header.image')">
-    <Image />
-  </Button>
-  <Button :class="{ active: activeTool === 'link' }" @click="changeActiveTool('link')" :title="$t('header.link')">
-    <Link />
-  </Button>
+    <Button v-else-if="tool.type === 'link' && !tool.hidden" :data-element="tool.dataElement" :class="{ active: activeTool === 'link' }" @click="changeActiveTool('link')" :title="$t('header.link')">
+      <Link />
+    </Button>
+  </template>
   <div class="divider pc"></div>
   <div v-if="showColors.includes(activeTool)" class="colors-container">
     <span class="cell-container">

+ 52 - 0
packages/webview/src/components/CustomButton/ActionButton.vue

@@ -0,0 +1,52 @@
+<template>
+  <div
+    class="button custom-button"
+    :data-element="dataElement"
+    :class="{
+      [type]: type,
+      disabled,
+      [className]: className
+    }"
+    @click="disabled || !onClick ? NOOP() : onClick($event)"
+    @dblclick="disabled ? NOOP : onDoubleClick"
+    @mouseup="disabled ? NOOP : onDoubleClick"
+    :disabled="disabled"
+    :title="title"
+  >
+    <template v-if="img">
+      <div v-if="isSVG" v-html="img" class="img"></div>
+      <img v-else :src="img" alt="" class="img">
+    </template>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted } from 'vue';
+import { useViewerStore } from '@/stores/modules/viewer'
+import { useDocumentStore } from '@/stores/modules/document'
+import { computed } from 'vue'
+import core from '@/core'
+const useViewer = useViewerStore()
+const useDocument = useDocumentStore()
+
+const { item } = defineProps(['item'])
+const {
+  type,
+  img,
+  dataElement,
+  onClick,
+  title,
+  disabled,
+  className
+} = item
+
+const isSVG = img?.trim().toLowerCase().startsWith('<svg');
+
+const NOOP = (e) => {
+  e?.stopPropagation()
+  e?.preventDefault()
+}
+</script>
+
+<style lang="scss">
+</style>

+ 130 - 0
packages/webview/src/components/CustomButton/CToolButton.vue

@@ -0,0 +1,130 @@
+<template>
+  <div
+    class="button custom-button"
+    :class="{
+      active: isActive,
+      [type]: type,
+      [className]: className,
+      'with-text': forms.includes(toolName)
+    }"
+    @click="onClick()"
+    :title="title"
+  >
+    <Highlight v-if="toolName === 'highlight'" />
+    <Underline v-if="toolName === 'underline'" />
+    <Strikeout v-if="toolName === 'strikeout'" />
+    <Squiggle v-if="toolName === 'squiggly'" />
+    <Ink v-if="toolName === 'ink'" />
+    <EllipseTool v-if="toolName === 'circle'" />
+    <RectangleTool v-if="toolName === 'square'" />
+    <ArrowTool v-if="toolName === 'arrow'" />
+    <LineTool v-if="toolName === 'line'" />
+    <Text v-if="toolName === 'freetext'" />
+    <Stamp v-if="toolName === 'stamp'" />
+    <Image v-if="toolName === 'image'" />
+    <Link v-if="toolName === 'link'" />
+    
+    <template v-if="toolName === 'textfield'">
+      <TextFieldIcon />
+      <span>{{ $t('header.textField') }}</span>
+    </template>
+    <template v-if="toolName === 'checkbox'">
+      <CheckBoxIcon />
+      <span>{{ $t('header.checkbox') }}</span>
+    </template>
+    <template v-if="toolName === 'radiobutton'">
+      <RadioButtonIcon />
+      <span>{{ $t('header.radioButton') }}</span>
+    </template>
+    <template v-if="toolName === 'listbox'">
+      <ListBoxIcon />
+      <span>{{ $t('header.listBox') }}</span>
+    </template>
+    <template v-if="toolName === 'combobox'">
+      <ComboBoxIcon />
+      <span>{{ $t('header.comboButton') }}</span>
+    </template>
+    <template v-if="toolName === 'pushbutton'">
+      <PushButtonIcon />
+      <span>{{ $t('header.button') }}</span>
+    </template>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted } from 'vue';
+import { useViewerStore } from '@/stores/modules/viewer'
+import { useDocumentStore } from '@/stores/modules/document'
+import { computed } from 'vue'
+import core from '@/core'
+const useViewer = useViewerStore()
+const useDocument = useDocumentStore()
+
+const { item } = defineProps(['item'])
+const {
+  type,
+  title,
+  disabled,
+  className,
+  toolName
+} = item
+
+const markups = ['highlight', 'underline', 'squiggly', 'strikeout']
+const shapes = ['square', 'circle', 'arrow', 'line']
+const annotations = ['ink', 'freetext', 'stamp', 'image', 'link']
+const forms = ['textfield', 'checkbox', 'radiobutton', 'listbox', 'combobox', 'pushbutton']
+const toolType = computed(() => {
+  if (markups.includes(toolName)) return 'markup'
+  if (shapes.includes(toolName)) return 'shape'
+  if (annotations.includes(toolName)) return 'annotation'
+  if (forms.includes(toolName)) return 'form'
+})
+
+const activeTool = computed(() => useDocument.getActiveTool)
+
+const isActive = computed(() => {
+  return toolName === activeTool.value
+})
+
+const changeActiveTool = (tool) => {
+  useDocument.setToolState(tool)
+  
+  useViewer.toggleActiveHand(false)
+  core.switchTool(0)
+  
+  core.switchAnnotationEditorMode(0)
+
+  if (tool === 'stamp') {
+    useViewer.toggleElement('stampPanel')
+  } else {
+    useViewer.closeElement('stampPanel')
+  }
+
+  if (activeTool.value === 'image') {
+    document.getElementById('signImageInput').click()
+  }
+}
+
+const changeMarkupTool = (tool) => {
+  useDocument.setMarkupToolState(tool)
+  changeActiveTool(tool)
+}
+
+const changeShapeTool = (tool) => {
+  useDocument.setShapeToolState(tool)
+  changeActiveTool(tool)
+}
+
+const onClick = (() => {
+  if (markups.includes(toolName)) changeMarkupTool(toolName)
+  if (shapes.includes(toolName)) changeShapeTool(toolName)
+  if (annotations.includes(toolName)) changeActiveTool(toolName)
+  if (forms.includes(toolName)) changeActiveTool(toolName)
+})
+</script>
+
+<style lang="scss" scoped>
+.custom-button {
+  width: auto;
+}
+</style>

+ 17 - 23
packages/webview/src/components/CustomButton/CustomButton.vue

@@ -1,31 +1,25 @@
 <template>
-  <Button
-    v-bind="{ ...item }"
-  >
-    <div v-if="isSVG" v-html="item.img" class="img"></div>
-    <img v-else :src="item.img" alt="" class="img">
-  </Button>
+  <ActionButton v-if="type === 'actionButton'" :item="item" />
+  <StatefulButton v-if="type === 'statefulButton'" :item="item" />
+  <ToggleButton v-if="type === 'toggleButton'" :item="item" />
+  <CToolButton v-if="type === 'toolButton'" :item="item" />
 </template>
 
 <script setup>
-  import { useViewerStore } from '@/stores/modules/viewer'
-  import { useDocumentStore } from '@/stores/modules/document'
-  import { computed } from 'vue'
-  import core from '@/core'
-  const useViewer = useViewerStore()
-  const useDocument = useDocumentStore()
-  
-  const { item } = defineProps(['item'])
-
-  const isSVG = item.img.trim().toLowerCase().startsWith('<svg');
+const { item } = defineProps(['item'])
+const { type } = item
 </script>
 
-<style>
-.img {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  max-width: 20px;
-  max-height: 20px;
+<style lang="scss">
+.custom-button {
+  width: 30px;
+  height: 30px;
+  .img {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 20px;
+    height: 20px;
+  }
 }
 </style>

+ 94 - 0
packages/webview/src/components/CustomButton/StatefulButton.vue

@@ -0,0 +1,94 @@
+<template>
+  <div
+    class="button custom-button"
+    :data-element="dataElement"
+    :class="{
+      active: isActive,
+      [type]: type,
+      disabled,
+      [className]: className
+    }"
+    @click="disabled || !onClick ? NOOP() : onClick($event)"
+    @dblclick="disabled ? NOOP : onDoubleClick"
+    @mouseup="disabled ? NOOP : onDoubleClick"
+    :disabled="disabled"
+    :title="title"
+  >
+    <template v-if="img">
+      <div v-if="isSVG" v-html="img" class="img"></div>
+      <img v-else :src="img" alt="" class="img">
+    </template>
+    {{ content() }}
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted } from 'vue';
+import { useViewerStore } from '@/stores/modules/viewer'
+import { useDocumentStore } from '@/stores/modules/document'
+import { computed } from 'vue'
+import core from '@/core'
+const useViewer = useViewerStore()
+const useDocument = useDocumentStore()
+
+const { item } = defineProps(['item'])
+const {
+  type,
+  img,
+  dataElement,
+  title,
+  disabled,
+  className,
+
+  initialState,
+  states,
+  mount,
+  unmount,
+} = item
+
+const isSVG = img?.trim().toLowerCase().startsWith('<svg');
+
+const NOOP = (e) => {
+  e?.stopPropagation()
+  e?.preventDefault()
+}
+
+// statefulButton 有状态按钮
+const activeState = ref(initialState);
+const stateOptions = states[activeState.value];
+const isActive = ref(false);
+
+const onClick = () => {
+  stateOptions.onClick(updateState, stateOptions);
+};
+
+const updateState = () => {
+  activeState.value = initialState;
+};
+
+const content = () => {
+  if (stateOptions.getContent instanceof Function) {
+    return stateOptions.getContent(stateOptions);
+  } else {
+    return stateOptions.getContent;
+  }
+};
+
+if (mount) {
+  onMounted(() => {
+    mount();
+  });
+}
+
+if (unmount) {
+  onUnmounted(() => {
+    unmount();
+  });
+}
+</script>
+
+<style lang="scss">
+.statefulButton {
+  background-color: var(--c-right-side-info-tip-bg);
+}
+</style>

+ 59 - 0
packages/webview/src/components/CustomButton/ToggleButton.vue

@@ -0,0 +1,59 @@
+<template>
+  <div
+    class="button custom-button"
+    :data-element="dataElement"
+    :class="{
+      active: isActive,
+      [type]: type,
+      disabled,
+      [className]: className
+    }"
+    @click="disabled || !onClick ? NOOP() : onClick($event)"
+    :disabled="disabled"
+    :title="title"
+  >
+    <template v-if="img">
+      <div v-if="isSVG" v-html="img" class="img"></div>
+      <img v-else :src="img" alt="" class="img">
+    </template>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted } from 'vue';
+import { useViewerStore } from '@/stores/modules/viewer'
+import { useDocumentStore } from '@/stores/modules/document'
+import { computed } from 'vue'
+import core from '@/core'
+const useViewer = useViewerStore()
+const useDocument = useDocumentStore()
+
+const { item } = defineProps(['item'])
+const {
+  type,
+  img,
+  dataElement,
+  title,
+  disabled,
+  className,
+  element
+} = item
+
+const isSVG = img?.trim().toLowerCase().startsWith('<svg');
+
+const NOOP = (e) => {
+  e?.stopPropagation()
+  e?.preventDefault()
+}
+
+const isActive = computed(() => {
+  return useViewer.isElementOpen(element)
+})
+
+const onClick = () => {
+  useViewer.toggleElement(element)
+}
+</script>
+
+<style lang="scss">
+</style>

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

@@ -193,7 +193,7 @@ onMounted(async () => {
     toggleButton: document.querySelector('.toggle-button')
   })
   let initialDoc = getHashParameters('d', '')
-  initialDoc = initialDoc ? JSON.parse(initialDoc) : ''
+  initialDoc = initialDoc ? JSON.parse(initialDoc) : '/example/Quick Start Guide for ComPDFKit Web Demo.pdf'
   initialDoc = Array.isArray(initialDoc) ? initialDoc : [initialDoc]
   const activeTab = useViewer.activeTab || 0
   initialDoc = initialDoc[activeTab]

+ 30 - 37
packages/webview/src/components/HeaderItems/HeaderItems.vue

@@ -1,15 +1,15 @@
 <template>
   <div v-if="toolMode !== 'compare'" class="header-items">
     <template v-for="(item, index) in items" :key="`${item.type}-${item.dataElement || index}`">
-      <ToggleElementButton v-if="item.type === 'toggleElementButton'" :item="item" :class="{ disabled: !load }" />
-      <ToolButton v-else-if="item.type === 'toolButton'" :item="item" />
+      <CustomButton v-if="item.name === 'customButton' && !item.hidden" :item="item" />
+      <ToggleElementButton v-else-if="item.type === 'toggleElementButton' && !item.hidden" :item="item" :class="{ disabled: !load }" :data-element="item.dataElement" />
+      <ToolButton v-else-if="item.type === 'toolButton' && !item.hidden" :item="item" :data-element="item.dataElement" />
       <FullScreenButton v-else-if="item.type === 'fullScreenButton' && !item.hidden" :item="item" :class="{ disabled: !load }" :data-element="item.dataElement" />
       <HandButton v-else-if="item.type === 'handToolButton' && !item.hidden" :item="item" :class="{ disabled: !load }" :data-element="item.dataElement" />
       <ZoomOverlay v-else-if="item.type === 'zoomOverlay' && !item.hidden" :data-element="item.dataElement" />
       <ThemeMode v-else-if="item.type === 'themeMode' && !item.hidden" :data-element="item.dataElement" />
-      <ViewRotationControls v-else-if="item.type === 'viewRotationControls'" />
+      <ViewRotationControls v-else-if="item.type === 'viewRotationControls' && !item.hidden" :data-element="item.dataElement" />
       <div v-else-if="['spacer', 'divider'].includes(item.type)" :class="item.type"></div>
-      <CustomButton v-else-if="item.type === 'customButton' && !item.hidden" :item="item" :data-element="item.dataElement" />
     </template>
 
     <!-- 工具类 下拉框的形式 -->
@@ -29,37 +29,40 @@
           </div>
         </template>
         <div class="drop-down narrower">
-          <div class="drop-item" :class="{ active: toolMode === 'view' }" @click="changeToolMode('view')">{{ $t('header.viewer') }}</div>
-          <div class="drop-item" :class="{ active: toolMode === 'annotation' }" @click="changeToolMode('annotation')">{{ $t('header.annotations') }}</div>
-          <div class="drop-item" :class="{ active: toolMode === 'form' }" @click="changeToolMode('form')">{{ $t('header.forms') }}</div>
-          <div class="drop-item" :class="{ active: toolMode === 'sign' }" @click="openSignCreatePanel()">{{ $t('header.signatures') }}</div>
-          <div class="drop-item" :class="{ active: toolMode === 'security' }" @click="changeToolMode('security')">{{ $t('header.security') }}</div>
-          <div class="drop-item" :class="{ active: toolMode === 'compare' }" @click="changeToolMode('compare')">{{ $t('header.compare') }}</div>
-          <div class="drop-item" :class="{ active: toolMode === 'editor' }" @click="changeToolMode('editor')">{{ $t('header.editor') }}</div>
+          <template v-for="(item, index) in tools" :key="`${item.dataElement || index}`">
+            <div v-if="item.element === 'view' && !item.hidden" :data-element="item.dataElement" class="drop-item" :class="{ active: toolMode === 'view' }" @click="changeToolMode('view')">{{ $t('header.viewer') }}</div>
+            <div v-else-if="item.element === 'annotation' && !item.hidden" :data-element="item.dataElement" class="drop-item" :class="{ active: toolMode === 'annotation' }" @click="changeToolMode('annotation')">{{ $t('header.annotations') }}</div>
+            <div v-else-if="item.element === 'form' && !item.hidden" :data-element="item.dataElement" class="drop-item" :class="{ active: toolMode === 'form' }" @click="changeToolMode('form')">{{ $t('header.forms') }}</div>
+            <div v-else-if="item.element === 'sign' && !item.hidden" :data-element="item.dataElement" class="drop-item" :class="{ active: toolMode === 'sign' }" @click="openSignCreatePanel()">{{ $t('header.signatures') }}</div>
+            <div v-else-if="item.element === 'security' && !item.hidden" :data-element="item.dataElement" class="drop-item" :class="{ active: toolMode === 'security' }" @click="changeToolMode('security')">{{ $t('header.security') }}</div>
+            <div v-else-if="item.element === 'compare' && !item.hidden" :data-element="item.dataElement" class="drop-item" :class="{ active: toolMode === 'compare' }" @click="changeToolMode('compare')">{{ $t('header.compare') }}</div>
+            <div v-else-if="item.element === 'editor' && !item.hidden" :data-element="item.dataElement" class="drop-item" :class="{ active: toolMode === 'editor' }" @click="changeToolMode('editor')">{{ $t('header.editor') }}</div>
+          </template>
         </div>
       </n-popover>
     </div>
     <div class="tool-container pc" :class="{ 'events-none': !load }">
       <div class="tool-menu">
         <template v-for="(item, index) in tools" :key="`${item.dataElement || index}`">
-          <div v-if="item.element === 'view' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'view', hidden: item.hidden }" @click="changeToolMode('view')">{{ $t('header.viewer') }}</div>
-          <div v-else-if="item.element === 'annotation' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'annotation', hidden: item.hidden }" @click="changeToolMode('annotation')">{{ $t('header.annotations') }}</div>
-          <div v-else-if="item.element === 'form' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'form', hidden: item.hidden }" @click="changeToolMode('form')">{{ $t('header.forms') }}</div>
-          <div v-else-if="item.element === 'sign' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'sign', hidden: item.hidden }" @click="openSignCreatePanel()">{{ $t('header.signatures') }}</div>
-          <div v-else-if="item.element === 'security' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'security', hidden: item.hidden }" @click="changeToolMode('security')">{{ $t('header.security') }}</div>
-          <div v-else-if="item.element === 'compare' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'compare', hidden: item.hidden }" @click="changeToolMode('compare')">{{ $t('header.compare') }}</div>
-          <div v-else-if="item.element === 'editor' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'editor', hidden: item.hidden }" @click="changeToolMode('editor')">{{ $t('header.editor') }}</div>
+          <div v-if="item.element === 'view' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'view' }" @click="changeToolMode('view')">{{ $t('header.viewer') }}</div>
+          <div v-else-if="item.element === 'annotation' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'annotation' }" @click="changeToolMode('annotation')">{{ $t('header.annotations') }}</div>
+          <div v-else-if="item.element === 'form' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'form' }" @click="changeToolMode('form')">{{ $t('header.forms') }}</div>
+          <div v-else-if="item.element === 'sign' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'sign' }" @click="openSignCreatePanel()">{{ $t('header.signatures') }}</div>
+          <div v-else-if="item.element === 'security' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'security' }" @click="changeToolMode('security')">{{ $t('header.security') }}</div>
+          <div v-else-if="item.element === 'compare' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'compare' }" @click="changeToolMode('compare')">{{ $t('header.compare') }}</div>
+          <div v-else-if="item.element === 'editor' && !item.hidden" :data-element="item.dataElement" class="item" :class="{ active: toolMode === 'editor' }" @click="changeToolMode('editor')">{{ $t('header.editor') }}</div>
         </template>
       </div>
     </div>
 
     <div class="right-container">
       <template v-for="(item, index) in rightItems" :key="`${item.type}-${item.dataElement || index}`">
-        <SearchButton v-if="item.type === 'searchButton' && !item.hidden" :item="item" :data-element="item.dataElement" />
-        <ToggleRightPanelButton v-if="item.type === 'toggleRightPanelButton' && !item.hidden" :item="item" :disabled="!load" :data-element="item.dataElement" />
-        <OpenFileButton v-if="item.type === 'openFileButton'" :item="item" />
-        <PageMode v-if="item.type === 'pageModeButton' && !item.hidden" :class="{ disabled: !load }" :data-element="item.dataElement" />
-        <CustomButton v-if="item.type === 'customButton' && !item.hidden" :item="item" :data-element="item.dataElement" />
+        <CustomButton v-if="item.name === 'customButton' && !item.hidden" :item="item" :data-element="item.dataElement" />
+        <SearchButton v-else-if="item.type === 'searchButton' && !item.hidden" :item="item" :data-element="item.dataElement" />
+        <ToggleRightPanelButton v-else-if="item.type === 'toggleRightPanelButton' && !item.hidden" :item="item" :disabled="!load" :data-element="item.dataElement" />
+        <OpenFileButton v-else-if="item.type === 'openFileButton' && !item.hidden" :item="item" :data-element="item.dataElement" />
+        <PageMode v-else-if="item.type === 'pageModeButton' && !item.hidden" :class="{ disabled: !load }" :data-element="item.dataElement" />
+        <div v-else-if="['spacer', 'divider'].includes(item.type)" :class="item.type"></div>
       </template>
       <Dropdown :class="{ disabled: !load }" :rightItems="rightItems" />
     </div>
@@ -68,7 +71,7 @@
   <!-- 文档对比模式 -->
   <div v-else class="header-items">
     <template v-for="(item, index) in items" :key="`${item.type}-${item.dataElement || index}`">
-      <FullScreenButton v-if="item.type === 'fullScreenButton'" :item="item" :class="{ disabled: compareStatus !== 'finished' }" />
+      <FullScreenButton v-if="item.type === 'fullScreenButton' && !item.hidden" :item="item" :class="{ disabled: compareStatus !== 'finished' }" :data-element="item.dataElement" />
     </template>
     <div class="divider"></div>
 
@@ -76,15 +79,15 @@
 
     <div class="right-container">
       <template v-for="(item, index) in rightItems" :key="`${item.type}-${item.dataElement || index}`">
-        <DownloadButton v-if="item.type === 'downloadButton'" :item="item" :class="{ disabled: compareStatus !== 'finished' && compareStatus !== 'next' }" />
-        <PrintButton v-if="item.type === 'printButton'" :item="item" :class="{ disabled: compareStatus !== 'finished' && compareStatus !== 'next' }" />
+        <DownloadButton v-if="item.type === 'downloadButton' && !item.hidden" :item="item" :class="{ disabled: compareStatus !== 'finished' && compareStatus !== 'next' }" :data-element="item.dataElement" />
+        <PrintButton v-if="item.type === 'printButton' && !item.hidden" :item="item" :class="{ disabled: compareStatus !== 'finished' && compareStatus !== 'next' }" :data-element="item.dataElement" />
       </template>
     </div>
   </div>
 </template>
 
 <script setup>
-import { ref, computed, getCurrentInstance } from 'vue'
+import { ref, computed, getCurrentInstance, watch } from 'vue'
 import { useViewerStore } from '@/stores/modules/viewer'
 import { useDocumentStore } from '@/stores/modules/document'
 import { NPopover } from 'naive-ui'
@@ -165,16 +168,6 @@ const openSignCreatePanel = () => {
 </script>
 
 <style lang="scss">
-  .test {
-    position: fixed;
-    right: 50px;
-    top: 50%;
-    transform: translateY(-50%);
-    padding: 20px;
-    background-color: bisque;
-    border-radius: 10px;
-    width: 200px;
-  }
   .header-items {
     display: flex;
     align-items: center;

+ 7 - 7
packages/webview/src/components/Toolbar/Toolbar.vue

@@ -2,15 +2,15 @@
   <div class="toolbar" :class="{ hidden: toolMode === 'view' || toolMode === 'sign' || (toolMode === 'compare' && compareStatus !== 'finished'), security: toolMode === 'security'}">
     <template v-for="(item, index) in toolItems[toolMode]" :key="`${item.type}-${item.dataElement || index}`">
       <!-- Annotation -->
-      <Annotate v-if="item.type === 'markup' && !item.hidden"/>
+      <Annotate v-if="item.type === 'annotation'" :item="item.tools" />
 
       <!-- Form -->
-      <TextFieldButton v-else-if="item.type === 'textFieldButton'" :item="item" />
-      <CheckBoxButton v-else-if="item.type === 'checkBoxButton'" :item="item" />
-      <RadioButton v-else-if="item.type === 'radioButton'" :item="item" />
-      <ListBox v-else-if="item.type === 'listBox'" :item="item" />
-      <ComboBox v-else-if="item.type === 'comboBox'" :item="item" />
-      <PushButton v-else-if="item.type === 'pushButton'" :item="item" />
+      <TextFieldButton v-else-if="item.type === 'textFieldButton' && !item.hidden" :item="item" :data-element="item.dataElement" />
+      <CheckBoxButton v-else-if="item.type === 'checkBoxButton' && !item.hidden" :item="item" :data-element="item.dataElement" />
+      <RadioButton v-else-if="item.type === 'radioButton' && !item.hidden" :item="item" :data-element="item.dataElement" />
+      <ListBox v-else-if="item.type === 'listBox' && !item.hidden" :item="item" :data-element="item.dataElement" />
+      <ComboBox v-else-if="item.type === 'comboBox' && !item.hidden" :item="item" :data-element="item.dataElement" />
+      <PushButton v-else-if="item.type === 'pushButton' && !item.hidden" :item="item" :data-element="item.dataElement" />
 
       <!-- Security -->
       <SecuritySelect v-else-if="item.type === 'securitySelect'" :item="item" />

+ 73 - 40
packages/webview/src/stores/modules/viewer.js

@@ -67,15 +67,13 @@ export const useViewerStore = defineStore({
         type: 'fullScreenButton',
         dataElement: 'fullScreenButton',
         element: 'fullScreenButton',
-        title: 'Full Screen',
-        hidden: false
+        title: 'Full Screen'
       },
       {
         type: 'handToolButton',
         dataElement: 'handToolButton',
         element: 'handToolButton',
-        title: 'Pan Tool',
-        hidden: false
+        title: 'Pan Tool'
       },
       {
         type: 'divider',
@@ -87,8 +85,7 @@ export const useViewerStore = defineStore({
         type: 'zoomOverlay',
         dataElement: 'zoomOverlayButton',
         element: 'zoomOverlay',
-        hiddenOnMobileDevice: true,
-        hidden: false
+        hiddenOnMobileDevice: true
       },
       {
         type: 'divider',
@@ -99,14 +96,12 @@ export const useViewerStore = defineStore({
       {
         type: 'themeMode',
         dataElement: 'themeMode',
-        element: 'themeMode',
-        hidden: false
+        element: 'themeMode'
       },
       {
         type: 'stickyNoteButton',
         dataElement: 'stickyNoteButton',
-        element: 'stickyNoteButton',
-        hidden: false,
+        element: 'stickyNoteButton'
       },
       {
         type: 'measureButton',
@@ -138,8 +133,7 @@ export const useViewerStore = defineStore({
         type: 'searchButton',
         dataElement: 'searchButton',
         element: 'searchButton',
-        title: 'Search',
-        hidden: false
+        title: 'Search'
       },
       {
         type: 'toggleRightPanelButton',
@@ -147,82 +141,121 @@ export const useViewerStore = defineStore({
         element: 'rightPanel',
         dataElement: 'rightPanelButton',
         title: 'Right Panel',
-        id: 'propertyPanelButton',
-        hidden: false
+        id: 'propertyPanelButton'
       },
       {
         type: 'pageModeButton',
         dataElement: 'pageModeButton',
-        element: 'pageModeButton',
-        hidden: false
+        element: 'pageModeButton'
       },
       {
         type: 'downloadButton',
         dataElement: 'downloadButton',
         element: 'downloadButton',
-        title: 'Download',
-        hidden: false
+        title: 'Download'
       },
       {
         type: 'flattenButton',
         dataElement: 'flattenButton',
         element: 'flattenButton',
-        title: 'Save as Flattened PDF',
-        hidden: false
+        title: 'Save as Flattened PDF'
       },
       {
         type: 'printButton',
         dataElement: 'printButton',
         element: 'printButton',
-        title: 'Print',
-        hidden: false
+        title: 'Print'
       }
     ],
     toolMode: 'view',
     tools: [
       {
         element: 'view',
-        dataElement: 'toolMenu-View',
-        hidden: false
+        dataElement: 'toolMenu-View'
       },
       {
         element: 'annotation',
-        dataElement: 'toolMenu-Annotation',
-        hidden: false
+        dataElement: 'toolMenu-Annotation'
       },
       {
         element: 'form',
-        dataElement: 'toolMenu-Form',
-        hidden: false
+        dataElement: 'toolMenu-Form'
       },
       {
         element: 'sign',
-        dataElement: 'toolMenu-Sign',
-        hidden: false
+        dataElement: 'toolMenu-Sign'
       },
       {
         element: 'security',
-        dataElement: 'toolMenu-Security',
-        hidden: false
+        dataElement: 'toolMenu-Security'
       },
       {
         element: 'compare',
-        dataElement: 'toolMenu-Compare',
-        hidden: false
+        dataElement: 'toolMenu-Compare'
       },
       {
         element: 'editor',
-        dataElement: 'toolMenu-Editor',
-        hidden: false
+        dataElement: 'toolMenu-Editor'
       }
     ],
     toolItems: {
       annotation: [
         {
-          type: 'markup',
-          dataElement: 'markup',
-          element: 'markup',
-          hidden: false,
+          type: 'annotation',
+          tools: [
+            {
+              type: 'note',
+              dataElement: 'note'
+            },
+            {
+              type: 'highlight',
+              dataElement: 'highlight'
+            },
+            {
+              type: 'underline',
+              dataElement: 'underline'
+            },
+            {
+              type: 'strikeout',
+              dataElement: 'strikeout'
+            },
+            {
+              type: 'squiggly',
+              dataElement: 'squiggly'
+            },
+            {
+              type: 'circle',
+              dataElement: 'circle'
+            },
+            {
+              type: 'square',
+              dataElement: 'square'
+            },
+            {
+              type: 'arrow',
+              dataElement: 'arrow'
+            },
+            {
+              type: 'line',
+              dataElement: 'line'
+            },
+            {
+              type: 'freetext',
+              dataElement: 'freetext'
+            },
+            {
+              type: 'stamp',
+              dataElement: 'stamp'
+            },
+            {
+              type: 'image',
+              dataElement: 'image'
+            },
+            {
+              type: 'link',
+              dataElement: 'link'
+            },
+          ]
         }
       ],
       form: [