浏览代码

!63 feat: 增加图层隐藏锁定功能,图层列表模式控制
Merge pull request !63 from dodu/dev-layers

奔跑的面条 3 年之前
父节点
当前提交
752cb744c0

+ 1 - 0
.eslintrc.js

@@ -22,6 +22,7 @@ module.exports = {
     'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
     'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
     'no-unused-vars': 'off',
+    'vue/no-unused-vars': 'off',
     'vue/multi-word-component-names': 'off',
     'vue/valid-template-root': 'off',
     'vue/no-mutating-props': 'off'

+ 16 - 8
src/enums/editPageEnum.ts

@@ -1,7 +1,7 @@
 // 鼠标点击左右键
 export enum MouseEventButton {
   LEFT = 1,
-  RIGHT = 2,
+  RIGHT = 2
 }
 
 // 页面拖拽键名
@@ -41,7 +41,15 @@ export enum MenuEnum {
   // 后退
   BACK = 'back',
   // 前进
-  FORWORD = 'forward'
+  FORWORD = 'forward',
+  // 锁定
+  LOCK = 'lock',
+  // 解除锁定
+  UNLOCK = 'unLock',
+  // 隐藏
+  HIDE = 'hide',
+  // 显示
+  SHOW = 'show'
 }
 
 // Win 键盘枚举
@@ -49,9 +57,9 @@ export enum WinKeyboard {
   CTRL = 'ctrl',
   SHIFT = 'shift',
   ALT = ' alt',
-  CTRL_SOURCE_KEY = "control",
-  SHIFT_SOURCE_KEY = "shift",
-  ALT_SOURCE_KEY = "alt"
+  CTRL_SOURCE_KEY = 'control',
+  SHIFT_SOURCE_KEY = 'shift',
+  ALT_SOURCE_KEY = 'alt'
 }
 
 // Mac 键盘枚举
@@ -60,7 +68,7 @@ export enum MacKeyboard {
   CTRL = '⌘',
   SHIFT = '⇧',
   ALT = '⌥',
-  CTRL_SOURCE_KEY = "⌘",
-  SHIFT_SOURCE_KEY = "⇧",
-  ALT_SOURCE_KEY = "⌥"
+  CTRL_SOURCE_KEY = '⌘',
+  SHIFT_SOURCE_KEY = '⇧',
+  ALT_SOURCE_KEY = '⌥'
 }

+ 16 - 2
src/plugins/icon.ts

@@ -57,7 +57,12 @@ import {
   ChevronDownOutline as ChevronDownOutlineIcon,
   Pulse as PulseIcon,
   Folder as FolderIcon,
-  FolderOpen as FolderOpenIcon
+  FolderOpen as FolderOpenIcon,
+  Image as ImageIcon,
+  Images as ImagesIcon,
+  List as ListIcon,
+  EyeOutline as EyeOutlineIcon,
+  EyeOffOutline as EyeOffOutlineIcon
 } from '@vicons/ionicons5'
 
 import {
@@ -211,7 +216,16 @@ const ionicons5 = {
   // 文件夹
   FolderIcon,
   // 文件夹打开
-  FolderOpenIcon
+  FolderOpenIcon,
+  // 图片
+  ImageIcon,
+  // 多个图片
+  ImagesIcon,
+  // 列表
+  ListIcon,
+  // 眼睛
+  EyeOutlineIcon,
+  EyeOffOutlineIcon
 }
 
 const carbon = {

+ 98 - 1
src/store/modules/chartEditStore/chartEditStore.ts

@@ -602,7 +602,8 @@ export const useChartEditStore = defineStore({
               ids.push(item.id)
             })
           } else {
-            (historyData[0] as CreateComponentGroupType).groupList.forEach(item => {
+            const group = historyData[0] as CreateComponentGroupType
+            group.groupList.forEach(item => {
               ids.push(item.id)
             })
           }
@@ -617,6 +618,32 @@ export const useChartEditStore = defineStore({
         }
         return
       }
+
+      switch (HistoryItem.actionType) {
+        // 锁定处理
+        case HistoryActionTypeEnum.LOCK:
+        case HistoryActionTypeEnum.UNLOCK:
+          if (!isForward) {
+            // 恢复原来状态
+            if (HistoryItem.actionType === HistoryActionTypeEnum.LOCK) historyData[0].status.lock = false
+            if (HistoryItem.actionType === HistoryActionTypeEnum.UNLOCK) historyData[0].status.lock = true
+            return
+          }
+          this.setLock(!historyData[0].status.lock, false)
+          break
+
+        // 隐藏处理
+        case HistoryActionTypeEnum.HIDE:
+        case HistoryActionTypeEnum.SHOW:
+          if (!isForward) {
+            // 恢复原来状态
+            if (HistoryItem.actionType === HistoryActionTypeEnum.HIDE) historyData[0].status.hide = false
+            if (HistoryItem.actionType === HistoryActionTypeEnum.SHOW) historyData[0].status.hide = true
+            return
+          }
+          this.setHide(!historyData[0].status.hide, false)
+          break
+      }
     },
     // * 撤回
     setBack() {
@@ -795,6 +822,76 @@ export const useChartEditStore = defineStore({
         loadingFinish()
       }
     },
+    // * 锁定
+    setLock(status: boolean = true, isHistory: boolean = true) {
+      try {
+        // 暂不支持多选
+        if (this.getTargetChart.selectId.length > 1) return
+
+        loadingStart()
+        const index: number = this.fetchTargetIndex()
+        if (index !== -1) {
+          // 更新状态
+          const targetItem = this.getComponentList[index]
+          targetItem.status.lock = status
+
+          // 历史记录
+          if (isHistory) {
+            chartHistoryStore.createLockHistory(
+              [targetItem],
+              status ? HistoryActionTypeEnum.LOCK : HistoryActionTypeEnum.UNLOCK
+            )
+          }
+          this.updateComponentList(index, targetItem)
+          loadingFinish()
+          return
+        }
+      } catch (value) {
+        loadingError()
+      }
+    },
+    // * 解除锁定
+    setUnLock(isHistory: boolean = true) {
+      this.setLock(false, isHistory)
+    },
+    // * 隐藏
+    setHide(status: boolean = true, isHistory: boolean = true) {
+      try {
+        // 暂不支持多选
+        if (this.getTargetChart.selectId.length > 1) return
+
+        loadingStart()
+        const index: number = this.fetchTargetIndex()
+        if (index !== -1) {
+          // 更新状态
+          const targetItem = this.getComponentList[index]
+          targetItem.status.hide = status
+
+          // 历史记录
+          if (isHistory) {
+            chartHistoryStore.createHideHistory(
+              [targetItem],
+              status ? HistoryActionTypeEnum.HIDE : HistoryActionTypeEnum.SHOW
+            )
+          }
+          this.updateComponentList(index, targetItem)
+          loadingFinish()
+
+          // 取消选择隐藏
+          if (status) {
+            const chartEditStore = useChartEditStore()
+            chartEditStore.setTargetSelectChart(undefined)
+          }
+          return
+        }
+      } catch (value) {
+        loadingError()
+      }
+    },
+    // * 显示
+    setShow(isHistory: boolean = true) {
+      this.setHide(false, isHistory)
+    },
     // ----------------
     // * 设置页面大小
     setPageSize(scale: number): void {

+ 6 - 5
src/store/modules/chartHistoryStore/chartHistoryDefine.ts

@@ -1,7 +1,4 @@
-import {
-  HistoryTargetTypeEnum,
-  HistoryActionTypeEnum
-} from './chartHistoryStore.d'
+import { HistoryTargetTypeEnum, HistoryActionTypeEnum } from './chartHistoryStore.d'
 
 export const historyActionTypeName = {
   [HistoryActionTypeEnum.ADD]: '新增图表',
@@ -18,6 +15,10 @@ export const historyActionTypeName = {
   [HistoryActionTypeEnum.GROUP]: '创建分组',
   [HistoryActionTypeEnum.UN_GROUP]: '解除分组',
   [HistoryActionTypeEnum.SELECT_HISTORY]: '选择记录',
-  
+  [HistoryActionTypeEnum.LOCK]: '锁定',
+  [HistoryActionTypeEnum.UNLOCK]: '解除锁定',
+  [HistoryActionTypeEnum.HIDE]: '隐藏',
+  [HistoryActionTypeEnum.SHOW]: '显示',
+
   [HistoryTargetTypeEnum.CANVAS]: '画布初始化'
 }

+ 10 - 1
src/store/modules/chartHistoryStore/chartHistoryStore.d.ts

@@ -2,6 +2,7 @@ import { CreateComponentType, CreateComponentGroupType } from '@/packages/index.
 import { EditCanvasType } from '@/store/modules/chartEditStore/chartEditStore.d'
 
 // 操作类型枚举
+
 export enum HistoryActionTypeEnum {
   // 新增
   ADD = 'add',
@@ -30,7 +31,15 @@ export enum HistoryActionTypeEnum {
   // 解组
   UN_GROUP = 'unGroup',
   // 选择历史记录
-  SELECT_HISTORY = 'selectHistory'
+  SELECT_HISTORY = 'selectHistory',
+  // 锁定
+  LOCK = 'lock',
+  // 解除锁定
+  UNLOCK = 'unLock',
+  // 隐藏
+  HIDE = 'hide',
+  // 显示
+  SHOW = 'show'
 }
 
 // 对象类型

+ 14 - 0
src/store/modules/chartHistoryStore/chartHistoryStore.ts

@@ -167,6 +167,20 @@ export const useChartHistoryStore = defineStore({
     // * 解除分组
     createUnGroupHistory(item: Array<CreateComponentType | CreateComponentGroupType>) {
       this.createStackItem(item, HistoryActionTypeEnum.UN_GROUP, HistoryTargetTypeEnum.CHART)
+    },
+    // * 锁定记录
+    createLockHistory(
+      item: Array<CreateComponentType | CreateComponentGroupType>,
+      type: HistoryActionTypeEnum.LOCK | HistoryActionTypeEnum.UNLOCK
+    ) {
+      this.createStackItem(item, type, HistoryTargetTypeEnum.CHART)
+    },
+    // * 隐藏记录
+    createHideHistory(
+      item: Array<CreateComponentType | CreateComponentGroupType>,
+      type: HistoryActionTypeEnum.HIDE | HistoryActionTypeEnum.SHOW
+    ) {
+      this.createStackItem(item, type, HistoryTargetTypeEnum.CHART)
     }
   }
 })

+ 15 - 8
src/styles/common/style.scss

@@ -1,7 +1,7 @@
-@import './var.scss';
-@import './format.scss';
-@import './animation.scss';
-@import './mixins/mixins.scss';
+@import "./var.scss";
+@import "./format.scss";
+@import "./animation.scss";
+@import "./mixins/mixins.scss";
 
 // 过度
 .go-transition {
@@ -49,14 +49,14 @@
 // 毛玻璃
 .go-background-filter {
   backdrop-filter: $--filter-blur-base;
-  @include fetch-bg-color('filter-color');
+  @include fetch-bg-color("filter-color");
   box-shadow: $--border-shadow;
 }
 
 // 毛玻璃
 .go-background-filter-shallow {
   backdrop-filter: $--filter-blur-base;
-  @include fetch-bg-color('filter-color-shallow');
+  @include fetch-bg-color("filter-color-shallow");
   box-shadow: $--border-shadow;
 }
 
@@ -68,7 +68,7 @@
 
 // 背景斑点需配合 @mixin background-image 使用
 .go-point-bg {
-  @include fetch-theme-custom('background-color', 'background-color1');
+  @include fetch-theme-custom("background-color", "background-color1");
   background-size: 15px 15px, 15px 15px;
 }
 
@@ -117,4 +117,11 @@
   .go-#{$typekey} {
     #{$type}: 0 !important;
   }
-}
+}
+
+.go-d-inline-block {
+  display: inline-block;
+}
+.go-d-block {
+  display: block;
+}

+ 4 - 11
src/views/chart/ContentBox/index.vue

@@ -10,14 +10,9 @@
           <slot name="icon"></slot>
         </div>
       </n-space>
-      <n-space>
+      <n-space align="center" style="gap: 4px">
         <slot name="top-right"></slot>
-        <n-icon
-          v-show="backIcon"
-          size="20"
-          class="go-cursor-pointer"
-          @click="backHandle"
-        >
+        <n-icon v-show="backIcon" size="20" class="go-cursor-pointer go-d-block" @click="backHandle">
           <chevron-back-outline-icon></chevron-back-outline-icon>
         </n-icon>
       </n-space>
@@ -151,7 +146,7 @@ $topOrBottomHeight: 40px;
     border-bottom: 1px solid;
     @include fetch-border-color('background-color1');
   }
-  
+
   .content {
     height: calc(100vh - #{$--header-height});
     overflow: hidden;
@@ -165,9 +160,7 @@ $topOrBottomHeight: 40px;
     height: calc(100vh - #{$--header-height} - #{$topOrBottomHeight});
   }
   .content-height-show-both {
-    height: calc(
-      100vh - #{$--header-height} - #{$topOrBottomHeight} - #{$topOrBottomHeight}
-    );
+    height: calc(100vh - #{$--header-height} - #{$topOrBottomHeight} - #{$topOrBottomHeight});
   }
 }
 </style>

+ 20 - 14
src/views/chart/ContentEdit/components/EditGroup/index.vue

@@ -85,26 +85,32 @@ const optionsHandle = (
   allList: MenuOptionsItemType[],
   targetInstance: CreateComponentType
 ) => {
-  // 多选
-  const moreMenuEnums = [MenuEnum.GROUP, MenuEnum.DELETE]
-  // 单选
-  const singleMenuEnums = [MenuEnum.UN_GROUP]
-
   const filter = (menulist: MenuEnum[]) => {
-    const list: MenuOptionsItemType[] = []
-    allList.forEach(item => {
-      if (menulist.includes(item.key as MenuEnum)) {
-        list.push(item)
-      }
-    })
-    return list
+    return allList.filter(i => menulist.includes(i.key as MenuEnum))
   }
 
   // 多选处理
   if (chartEditStore.getTargetChart.selectId.length > 1) {
-    return filter(moreMenuEnums)
+    return filter([MenuEnum.GROUP, MenuEnum.DELETE])
   } else {
-    return [...filter(singleMenuEnums), divider(), ...targetList]
+    const statusMenuEnums: MenuEnum[] = []
+    if (targetInstance.status.lock) {
+      statusMenuEnums.push(MenuEnum.LOCK)
+    } else {
+      statusMenuEnums.push(MenuEnum.UNLOCK)
+    }
+    if (targetInstance.status.hide) {
+      statusMenuEnums.push(MenuEnum.HIDE)
+    } else {
+      statusMenuEnums.push(MenuEnum.SHOW)
+    }
+    // 单选
+    const singleMenuEnums = [MenuEnum.UN_GROUP]
+    return [
+      ...filter(singleMenuEnums),
+      divider(),
+      ...targetList.filter(i => !statusMenuEnums.includes(i.key as MenuEnum))
+    ]
   }
 }
 

+ 22 - 5
src/views/chart/ContentEdit/components/EditHistory/index.vue

@@ -48,8 +48,19 @@ import {
   HistoryActionTypeEnum
 } from '@/store/modules/chartHistoryStore/chartHistoryStore.d'
 
-const { DesktopOutlineIcon, PencilIcon, TrashIcon, CopyIcon, LayersIcon, DuplicateIcon, HelpOutlineIcon } =
-  icon.ionicons5
+const {
+  DesktopOutlineIcon,
+  PencilIcon,
+  TrashIcon,
+  CopyIcon,
+  LayersIcon,
+  DuplicateIcon,
+  HelpOutlineIcon,
+  LockClosedOutlineIcon,
+  LockOpenOutlineIcon,
+  EyeOffOutlineIcon,
+  EyeOutlineIcon
+} = icon.ionicons5
 const { StackedMoveIcon, Carbon3DCursorIcon, Carbon3DSoftwareIcon } = icon.carbon
 
 const chartHistoryStoreStore = useChartHistoryStore()
@@ -83,6 +94,14 @@ const iconHandle = (e: HistoryItemType) => {
       return Carbon3DCursorIcon
     case HistoryActionTypeEnum.UN_GROUP:
       return Carbon3DSoftwareIcon
+    case HistoryActionTypeEnum.LOCK:
+      return LockClosedOutlineIcon
+    case HistoryActionTypeEnum.UNLOCK:
+      return LockOpenOutlineIcon
+    case HistoryActionTypeEnum.HIDE:
+      return EyeOffOutlineIcon
+    case HistoryActionTypeEnum.SHOW:
+      return EyeOutlineIcon
     default:
       return PencilIcon
   }
@@ -109,9 +128,7 @@ const options = computed(() => {
     }
   })
 
-  return reverse(options.filter(item => {
-    return item.label
-  }))
+  return reverse(options.filter(item => item.label))
 })
 </script>
 

+ 11 - 1
src/views/chart/ContentEdit/components/EditShapeBox/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="go-shape-box">
+  <div class="go-shape-box" :class="{ lock: item.status.lock, hide: item.status.hide }">
     <slot></slot>
     <!-- 锚点 -->
     <template v-if="!hiddenPoint">
@@ -55,12 +55,14 @@ const themeColor = computed(() => {
 
 // 计算当前选中目标
 const hover = computed(() => {
+  if (props.item.status.lock) return false
   return props.item.id === chartEditStore.getTargetChart.hoverId
 })
 
 // 兼容多值场景
 const select = computed(() => {
   const id = props.item.id
+  if (props.item.status.lock) return false
   return chartEditStore.getTargetChart.selectId.find((e: string) => e === id)
 })
 </script>
@@ -70,6 +72,14 @@ const select = computed(() => {
   position: absolute;
   cursor: move;
 
+  &.lock {
+    cursor: default !important;
+  }
+
+  &.hide {
+    display: none;
+  }
+
   /* 锚点 */
   .shape-point {
     z-index: 1;

+ 5 - 1
src/views/chart/ContentEdit/hooks/useDrag.hook.ts

@@ -140,7 +140,9 @@ export const mousedownBoxSelect = (e: MouseEvent, item?: CreateComponentType | C
           targetAttr.x1 - selectAttr.x1 >= 0 &&
           targetAttr.y1 - selectAttr.y1 >= 0 &&
           targetAttr.x2 - selectAttr.x2 <= 0 &&
-          targetAttr.y2 - selectAttr.y2 <= 0
+          targetAttr.y2 - selectAttr.y2 <= 0 &&
+          !item.status.lock &&
+          !item.status.hide
         ) {
           isSelect = true
           chartEditStore.setTargetSelectChart(item.id, true)
@@ -166,6 +168,7 @@ export const useMouseHandle = () => {
   const mouseClickHandle = (e: MouseEvent, item: CreateComponentType | CreateComponentGroupType) => {
     e.preventDefault()
     e.stopPropagation()
+    if (item.status.lock) return
     // 若此时按下了 CTRL, 表示多选
     if (
       window.$KeyboardActive?.has(WinKeyboard.CTRL_SOURCE_KEY) ||
@@ -185,6 +188,7 @@ export const useMouseHandle = () => {
   const mousedownHandle = (e: MouseEvent, item: CreateComponentType | CreateComponentGroupType) => {
     e.preventDefault()
     e.stopPropagation()
+    if (item.status.lock) return
     onClickOutSide()
     // 按下左键 + CTRL
     if (

+ 13 - 15
src/views/chart/ContentEdit/index.vue

@@ -114,24 +114,22 @@ const optionsHandle = (
   allList: MenuOptionsItemType[],
   targetInstance: CreateComponentType
 ) => {
-  // 多选
-  const moreMenuEnums = [MenuEnum.GROUP, MenuEnum.DELETE]
-  // 单选
-  const singleMenuEnums = targetList
-
   // 多选处理
   if (chartEditStore.getTargetChart.selectId.length > 1) {
-    const list: MenuOptionsItemType[] = []
-
-    allList.forEach(item => {
-      // 成组
-      if (moreMenuEnums.includes(item.key as MenuEnum)) {
-        list.push(item)
-      }
-    })
-    return list
+    return allList.filter(i => [MenuEnum.GROUP, MenuEnum.DELETE].includes(i.key as MenuEnum))
+  }
+  const statusMenuEnums: MenuEnum[] = []
+  if (targetInstance.status.lock) {
+    statusMenuEnums.push(MenuEnum.LOCK)
+  } else {
+    statusMenuEnums.push(MenuEnum.UNLOCK)
+  }
+  if (targetInstance.status.hide) {
+    statusMenuEnums.push(MenuEnum.HIDE)
+  } else {
+    statusMenuEnums.push(MenuEnum.SHOW)
   }
-  return singleMenuEnums
+  return targetList.filter(i => !statusMenuEnums.includes(i.key as MenuEnum))
 }
 
 // 主题色

+ 61 - 12
src/views/chart/ContentLayers/components/LayersGroupListItem/index.vue

@@ -2,7 +2,7 @@
   <div class="go-content-layers-group-list-item">
     <div
       class="root-item-content"
-      :class="{ hover: hover, select: select }"
+      :class="{ hover: hover, select: select, 'list-mini': layerMode === 'text' }"
       @click="clickHandle($event)"
       @mousedown="groupMousedownHandle($event)"
       @mouseenter="mouseenterHandle(componentGroupData)"
@@ -18,11 +18,13 @@
             <folder-icon></folder-icon>
           </template>
         </n-icon>
-        <n-ellipsis>
+        <n-ellipsis style="margin-right: auto">
           <n-text class="go-ml-2 list-text" :depth="2">
             {{ componentGroupData.chartConfig.title }}
           </n-text>
         </n-ellipsis>
+        <n-icon size="12" class="list-status-icon" :component="LockClosedOutlineIcon" v-if="status.lock" />
+        <n-icon size="12" class="list-status-icon" :component="EyeOffOutlineIcon" v-if="status.hide" />
       </div>
       <div :class="{ 'select-modal': select }"></div>
     </div>
@@ -31,6 +33,7 @@
         v-for="element in componentGroupData.groupList"
         :key="element.id"
         :componentData="element"
+        :layer-mode="layerMode"
         @mousedown="mousedownHandle($event, element, componentGroupData.id)"
         @mouseenter="mouseenterHandle(element)"
         @mouseleave="mouseleaveHandle(element)"
@@ -50,13 +53,21 @@ import { useContextMenu, divider } from '@/views/chart/hooks/useContextMenu.hook
 import { MenuOptionsItemType } from '@/views/chart/hooks/useContextMenu.hook.d'
 import { CreateComponentType, CreateComponentGroupType } from '@/packages/index.d'
 import { LayersListItem } from '../LayersListItem'
-import throttle from 'lodash/throttle'
 import { icon } from '@/plugins'
+import { LayerModeEnum } from '../../enums'
+
+const { LockClosedOutlineIcon, EyeOffOutlineIcon } = icon.ionicons5
 
 const props = defineProps({
   componentGroupData: {
     type: Object as PropType<CreateComponentGroupType>,
     required: true
+  },
+  layerMode: {
+    type: Object as PropType<LayerModeEnum>,
+    default(): LayerModeEnum {
+      return 'thumbnail'
+    }
   }
 })
 
@@ -84,20 +95,29 @@ const optionsHandle = (
   targetInstance: CreateComponentType
 ) => {
   const filter = (menulist: MenuEnum[]) => {
-    const list: MenuOptionsItemType[] = []
-    allList.forEach(item => {
-      if (menulist.includes(item.key as MenuEnum)) {
-        list.push(item)
-      }
-    })
-    return list
+    return allList.filter(i => menulist.includes(i.key as MenuEnum))
   }
 
   // 多选处理
   if (chartEditStore.getTargetChart.selectId.length > 1) {
     return filter([MenuEnum.GROUP])
   } else {
-    return [...filter([MenuEnum.UN_GROUP]), divider(), ...targetList]
+    const statusMenuEnums: MenuEnum[] = []
+    if (targetInstance.status.lock) {
+      statusMenuEnums.push(MenuEnum.LOCK)
+    } else {
+      statusMenuEnums.push(MenuEnum.UNLOCK)
+    }
+    if (targetInstance.status.hide) {
+      statusMenuEnums.push(MenuEnum.HIDE)
+    } else {
+      statusMenuEnums.push(MenuEnum.SHOW)
+    }
+    return [
+      ...filter([MenuEnum.UN_GROUP]),
+      divider(),
+      ...targetList.filter(i => !statusMenuEnums.includes(i.key as MenuEnum))
+    ]
   }
 }
 
@@ -125,6 +145,10 @@ const hover = computed(() => {
   return props.componentGroupData.id === chartEditStore.getTargetChart.hoverId
 })
 
+const status = computed(() => {
+  return props.componentGroupData.status
+})
+
 // 组点击事件
 const groupMousedownHandle = (e: MouseEvent) => {
   onClickOutSide()
@@ -148,7 +172,11 @@ const groupMousedownHandle = (e: MouseEvent) => {
 }
 
 // 公共点击事件
-const mousedownHandle = (e: MouseEvent, componentInstance: CreateComponentType | CreateComponentGroupType, id?: string) => {
+const mousedownHandle = (
+  e: MouseEvent,
+  componentInstance: CreateComponentType | CreateComponentGroupType,
+  id?: string
+) => {
   e.preventDefault()
   e.stopPropagation()
 
@@ -169,6 +197,7 @@ const mouseleaveHandle = (componentInstance: CreateComponentType | CreateCompone
 
 <style lang="scss" scoped>
 $centerHeight: 52px;
+$centerMiniHeight: 28px;
 $textSize: 10px;
 
 @include go(content-layers-group-list-item) {
@@ -178,6 +207,11 @@ $textSize: 10px;
   margin-bottom: 5px;
   @extend .go-transition-quick;
 
+  :deep(.go-content-layers-list-item) {
+    margin-right: 0 !important;
+    width: 95% !important;
+  }
+
   .root-item-content {
     height: $centerHeight;
     cursor: pointer;
@@ -196,6 +230,17 @@ $textSize: 10px;
         border: 1px solid v-bind('themeColor') !important;
       }
     }
+
+    // mini样式
+    &.list-mini {
+      height: $centerMiniHeight;
+      .item-content {
+        height: calc(#{$centerMiniHeight} - 10px) !important;
+      }
+      .select-modal {
+        height: calc(#{$centerMiniHeight} + 2px) !important;
+      }
+    }
   }
   .select-modal,
   .item-content {
@@ -220,5 +265,9 @@ $textSize: 10px;
     padding-left: 6px;
     font-size: $textSize;
   }
+
+  .list-status-icon {
+    margin-left: 3px;
+  }
 }
 </style>

+ 44 - 13
src/views/chart/ContentLayers/components/LayersListItem/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="go-content-layers-list-item" :class="{ hover: hover, select: select }">
+  <div class="go-content-layers-list-item" :class="{ hover: hover, select: select, 'list-mini': layerMode === 'text' }">
     <div class="go-flex-center item-content">
       <n-image
         class="list-img"
@@ -8,21 +8,27 @@
         :src="image"
         :fallback-src="requireErrorImg()"
       ></n-image>
-      <n-ellipsis>
+      <n-ellipsis style="margin-right: auto">
         <n-text class="list-text" :depth="2">
           {{ props.componentData.chartConfig.title }}
         </n-text>
       </n-ellipsis>
+      <n-icon size="12" class="list-status-icon" :component="LockClosedOutlineIcon" v-if="status.lock" />
+      <n-icon size="12" class="list-status-icon" :component="EyeOffOutlineIcon" v-if="status.hide" />
     </div>
     <div :class="{ 'select-modal': select }"></div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { toRefs, computed } from 'vue'
+import { toRefs, computed, PropType } from 'vue'
 import { requireErrorImg } from '@/utils'
 import { useDesignStore } from '@/store/modules/designStore/designStore'
 import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
+import { LayerModeEnum } from '../../enums'
+
+import { icon } from '@/plugins'
+const { LockClosedOutlineIcon, EyeOffOutlineIcon } = icon.ionicons5
 
 // 全局颜色
 const designStore = useDesignStore()
@@ -37,6 +43,12 @@ const props = defineProps({
   componentData: {
     type: Object,
     required: true
+  },
+  layerMode: {
+    type: Object as PropType<LayerModeEnum>,
+    default(): LayerModeEnum {
+      return 'thumbnail'
+    }
   }
 })
 
@@ -52,10 +64,15 @@ const select = computed(() => {
 const hover = computed(() => {
   return props.componentData.id === chartEditStore.getTargetChart.hoverId
 })
+
+const status = computed(() => {
+  return props.componentData.status
+})
 </script>
 
 <style lang="scss" scoped>
 $centerHeight: 52px;
+$centerMiniHeight: 28px;
 $textSize: 10px;
 
 @include go(content-layers-list-item) {
@@ -72,15 +89,7 @@ $textSize: 10px;
   &:hover {
     @include fetch-bg-color('background-color4');
   }
-  /* 选中 */
-  &.select {
-    border: 1px solid v-bind('themeColor');
-    /* 需要设置最高级,覆盖 hover 的颜色 */
-    background-color: rgba(0, 0, 0, 0);
-    .list-img {
-      border: 1px solid v-bind('themeColor') !important;
-    }
-  }
+
   .select-modal,
   .item-content {
     position: absolute;
@@ -94,24 +103,46 @@ $textSize: 10px;
     width: calc(100% - 10px);
     height: calc(100% - 10px);
   }
+
   .select-modal {
     width: 100%;
     height: 100%;
     opacity: 0.3;
     background-color: v-bind('themeColor');
   }
+
   .list-img {
     flex-shrink: 0;
     height: $centerHeight;
     border-radius: 5px;
     overflow: hidden;
-    border: 1px solid;
+    border: none !important;
     padding: 2px;
     @include hover-border-color('hover-border-color');
   }
+
   .list-text {
     padding-left: 6px;
     font-size: $textSize;
   }
+
+  .list-status-icon {
+    margin-left: 3px;
+  }
+
+  /* 选中样式 */
+  &.select {
+    border: 1px solid v-bind('themeColor');
+    /* 需要设置最高级,覆盖 hover 的颜色 */
+    background-color: rgba(0, 0, 0, 0);
+    // .list-img {
+    //   border: 1px solid v-bind('themeColor') !important;
+    // }
+  }
+
+  // mini样式
+  &.list-mini {
+    height: $centerMiniHeight;
+  }
 }
 </style>

+ 1 - 0
src/views/chart/ContentLayers/enums.ts

@@ -0,0 +1 @@
+export type LayerModeEnum = 'thumbnail' | 'text'

+ 53 - 15
src/views/chart/ContentLayers/index.vue

@@ -8,24 +8,49 @@
     @mousedown="boxMousedownHandle($event)"
   >
     <template #icon>
-      <n-icon size="16" :depth="2">
-        <component :is="LayersIcon"></component>
-      </n-icon>
+      <n-icon size="16" :depth="2" :component="LayersIcon" />
     </template>
+
+    <template #top-right>
+      <n-button-group style="display: flex">
+        <n-button
+          v-for="(item, index) in layerModeEnumList"
+          :key="index"
+          ghost
+          size="tiny"
+          :type="layerMode === item.value ? 'primary' : 'tertiary'"
+          @click="layerMode = item.value as LayerModeEnum"
+        >
+          <n-tooltip :show-arrow="false" trigger="hover">
+            <template #trigger>
+              <n-icon size="14" :component="item.icon" />
+            </template>
+            {{ item.label }}
+          </n-tooltip>
+        </n-button>
+      </n-button-group>
+    </template>
+
     <!-- 图层内容 -->
     <n-space v-if="reverseList.length === 0" justify="center">
       <n-text class="not-layer-text">暂无图层~</n-text>
     </n-space>
+
     <!-- https://github.com/SortableJS/vue.draggable.next -->
     <draggable item-key="id" v-model="layerList" ghostClass="ghost" @change="onMoveCallback">
       <template #item="{ element }">
         <div class="go-content-layer-box">
           <!-- 组合 -->
-          <layers-group-list-item v-if="element.isGroup" :componentGroupData="element"></layers-group-list-item>
+          <layers-group-list-item
+            v-if="element.isGroup"
+            :componentGroupData="element"
+            :layer-mode="layerMode"
+          ></layers-group-list-item>
           <!-- 单组件 -->
           <layers-list-item
             v-else
             :componentData="element"
+            :layer-mode="layerMode"
             @mousedown="mousedownHandle($event, element)"
             @mouseenter="mouseenterHandle(element)"
             @mouseleave="mouseleaveHandle(element)"
@@ -52,15 +77,21 @@ import { MenuEnum, MouseEventButton, WinKeyboard, MacKeyboard } from '@/enums/ed
 
 import { LayersListItem } from './components/LayersListItem/index'
 import { LayersGroupListItem } from './components/LayersGroupListItem/index'
+import { LayerModeEnum } from './enums'
 
 import { icon } from '@/plugins'
 
-const { LayersIcon } = icon.ionicons5
+const { LayersIcon, ImageIcon, ImagesIcon, ListIcon } = icon.ionicons5
 const chartLayoutStore = useChartLayoutStore()
 const chartEditStore = useChartEditStore()
 const { handleContextMenu, onClickOutSide } = useContextMenu()
 
 const layerList = ref<any>([])
+const layerModeEnumList = [
+  { label: '缩略图', icon: ImagesIcon, value: 'thumbnail' },
+  { label: '文本列表', icon: ListIcon, value: 'text' }
+]
+const layerMode = ref<LayerModeEnum>('thumbnail')
 
 // 逆序展示
 const reverseList = computed(() => {
@@ -83,16 +114,20 @@ const optionsHandle = (
 ) => {
   // 多选处理
   if (chartEditStore.getTargetChart.selectId.length > 1) {
-    const list: MenuOptionsItemType[] = []
-    targetList.forEach(item => {
-      // 成组
-      if (item.key === MenuEnum.GROUP) {
-        list.push(item)
-      }
-    })
-    return list
+    return targetList.filter(i => i.key === MenuEnum.GROUP)
   }
-  return targetList
+  const statusMenuEnums: MenuEnum[] = []
+  if (targetInstance.status.lock) {
+    statusMenuEnums.push(MenuEnum.LOCK)
+  } else {
+    statusMenuEnums.push(MenuEnum.UNLOCK)
+  }
+  if (targetInstance.status.hide) {
+    statusMenuEnums.push(MenuEnum.HIDE)
+  } else {
+    statusMenuEnums.push(MenuEnum.SHOW)
+  }
+  return targetList.filter(item => !statusMenuEnums.includes(item.key as MenuEnum))
 }
 
 // 缩小
@@ -159,7 +194,7 @@ const mouseleaveHandle = (item: CreateComponentType) => {
 </script>
 
 <style lang="scss" scoped>
-$wight: 170px;
+$wight: 200px;
 @include go(content-layers) {
   width: $wight;
   flex-shrink: 0;
@@ -177,5 +212,8 @@ $wight: 170px;
   .ghost {
     opacity: 0;
   }
+  .go-layer-mode-active {
+    color: #51d6a9;
+  }
 }
 </style>

+ 41 - 2
src/views/chart/hooks/useContextMenu.hook.ts

@@ -7,7 +7,18 @@ import { MenuOptionsItemType } from './useContextMenu.hook.d'
 import { MenuEnum } from '@/enums/editPageEnum'
 import cloneDeep from 'lodash/cloneDeep'
 
-const { CopyIcon, CutIcon, ClipboardOutlineIcon, TrashIcon, ChevronDownIcon, ChevronUpIcon } = icon.ionicons5
+const {
+  CopyIcon,
+  CutIcon,
+  ClipboardOutlineIcon,
+  TrashIcon,
+  ChevronDownIcon,
+  ChevronUpIcon,
+  LockOpenOutlineIcon,
+  LockClosedOutlineIcon,
+  EyeOutlineIcon,
+  EyeOffOutlineIcon
+} = icon.ionicons5
 const { UpToTopIcon, DownToBottomIcon, PaintBrushIcon, Carbon3DSoftwareIcon, Carbon3DCursorIcon } = icon.carbon
 
 const chartEditStore = useChartEditStore()
@@ -17,7 +28,7 @@ const chartEditStore = useChartEditStore()
  * @param {number} n > 2
  * @returns
  */
-export const divider = (n:number = 3) => {
+export const divider = (n: number = 3) => {
   return {
     type: 'divider',
     key: `d${n}`
@@ -26,6 +37,34 @@ export const divider = (n:number = 3) => {
 
 // * 默认单组件选项
 export const defaultOptions: MenuOptionsItemType[] = [
+  {
+    label: '锁定',
+    key: MenuEnum.LOCK,
+    icon: renderIcon(LockClosedOutlineIcon),
+    fnHandle: chartEditStore.setLock
+  },
+  {
+    label: '解除锁定',
+    key: MenuEnum.UNLOCK,
+    icon: renderIcon(LockOpenOutlineIcon),
+    fnHandle: chartEditStore.setUnLock
+  },
+  {
+    label: '隐藏',
+    key: MenuEnum.HIDE,
+    icon: renderIcon(EyeOffOutlineIcon),
+    fnHandle: chartEditStore.setHide
+  },
+  {
+    label: '显示',
+    key: MenuEnum.SHOW,
+    icon: renderIcon(EyeOutlineIcon),
+    fnHandle: chartEditStore.setShow
+  },
+  {
+    type: 'divider',
+    key: 'd0'
+  },
   {
     label: '复制',
     key: MenuEnum.COPY,