Explorar el Código

feat: 新增复制粘贴功能

MTrun hace 3 años
padre
commit
47a7ce9d6e

+ 3 - 3
src/packages/components/Charts/Bars/BarCommon/config.ts

@@ -5,9 +5,9 @@ import { ConfigType, CreateComponentType } from '@/packages/index.d'
 import omit from 'lodash/omit'
 
 export default class Config implements CreateComponentType {
-  public id: string = getUUID()
-  public key: string = BarCommonConfig.key
-
+  public id = getUUID()
+  public key = BarCommonConfig.key
+  public rename = undefined
   public chartData: Exclude<ConfigType, ['node']> = omit(BarCommonConfig, ['node'])
 
   public attr = { x: 0, y: 0, w: 500, h: 300 }

+ 1 - 0
src/packages/index.d.ts

@@ -16,6 +16,7 @@ export type ConfigType = {
 export interface CreateComponentType {
   id: string
   key: string
+  rename?: string
   attr: { x: number; y: number; w: number; h: number }
   chartData: ConfigType
   option: object

+ 12 - 3
src/plugins/icon.ts

@@ -39,7 +39,9 @@ import {
   Home as HomeIcon,
   Card as CardIcon,
   ChevronUp as ChevronUpIcon,
-  ChevronDown as ChevronDownIcon
+  ChevronDown as ChevronDownIcon,
+  TimeOutline as TimeOutlineIcon,
+  ClipboardOutline as ClipboardOutlineIcon
 } from '@vicons/ionicons5'
 
 import {
@@ -53,10 +55,11 @@ import {
   DicomOverlay as DicomOverlayIcon,
   UpToTop as UpToTopIcon,
   DownToBottom as DownToBottomIcon,
+  StackedMove as StackedMoveIcon
 } from '@vicons/carbon'
 
 const ionicons5 = {
-  // 帮助
+  // 帮助(问号)
   HelpOutlineIcon,
   // 添加
   DuplicateIcon,
@@ -137,6 +140,10 @@ const ionicons5 = {
   ChevronUpIcon,
   // 下移
   ChevronDownIcon,
+  // 时间
+  TimeOutlineIcon,
+  // 剪贴板
+  ClipboardOutlineIcon
 }
 
 const carbon = {
@@ -159,7 +166,9 @@ const carbon = {
   // 置顶
   UpToTopIcon,
   // 置底
-  DownToBottomIcon
+  DownToBottomIcon,
+  // 移动
+  StackedMoveIcon
 }
 
 // https://www.xicons.org/#/ 还有很多

+ 2 - 0
src/store/modules/chartEditStore/chartEditStore.d.ts

@@ -60,6 +60,7 @@ export enum ChartEditStoreEnum {
   RIGHT_MENU_SHOW = 'rightMenuShow',
   MOUSE_POSITION = 'mousePosition',
   TARGET_CHART = 'targetChart',
+  RECORD_CHARTS = 'recordCharts',
   COMPONENT_LIST = 'componentList'
 }
 
@@ -69,5 +70,6 @@ export interface chartEditStoreType {
   [ChartEditStoreEnum.RIGHT_MENU_SHOW]: boolean
   [ChartEditStoreEnum.MOUSE_POSITION]: MousePositionType
   [ChartEditStoreEnum.TARGET_CHART]: TargetChartType
+  [ChartEditStoreEnum.RECORD_CHARTS]?: CreateComponentType | CreateComponentType[]
   [ChartEditStoreEnum.COMPONENT_LIST]: CreateComponentType[]
 }

+ 95 - 20
src/store/modules/chartEditStore/chartEditStore.ts

@@ -1,7 +1,8 @@
 import { defineStore } from 'pinia'
-import debounce from 'lodash/debounce'
-import { loadingStart, loadingFinish, loadingError } from '@/utils'
+import { getUUID, loadingStart, loadingFinish, loadingError } from '@/utils'
 import { CreateComponentType } from '@/packages/index.d'
+import debounce from 'lodash/debounce'
+import cloneDeep from 'lodash/cloneDeep'
 import {
   chartEditStoreType,
   EditCanvasType,
@@ -9,6 +10,12 @@ import {
   TargetChartType
 } from './chartEditStore.d'
 
+// 记录记录
+import { useChartHistoryStoreStore } from '@/store/modules/chartHistoryStore/chartHistoryStore'
+import { HistoryActionTypeEnum } from '@/store/modules/chartHistoryStore/chartHistoryStore.d'
+
+const chartHistoryStoreStore = useChartHistoryStoreStore()
+
 // 编辑区域内容
 export const useChartEditStoreStore = defineStore({
   id: 'useChartEditStoreStore',
@@ -24,7 +31,7 @@ export const useChartEditStoreStore = defineStore({
       height: 1080,
       // 偏移量
       offset: 20,
-      // 系统控制缩放
+      // 系统控制缩放 
       scale: 1,
       // 用户控制的缩放
       userScale: 1,
@@ -45,6 +52,8 @@ export const useChartEditStoreStore = defineStore({
       hoverId: undefined,
       selectId: undefined
     },
+    // 记录临时数据(复制等)
+    recordCharts: undefined,
     // 图表数组
     componentList: []
   }),
@@ -61,6 +70,9 @@ export const useChartEditStoreStore = defineStore({
     getTargetChart():TargetChartType {
       return this.targetChart
     },
+    getRecordCharts(): CreateComponentType | CreateComponentType[] | undefined {
+      return this.recordCharts
+    },
     getComponentList(): CreateComponentType[] {
       return this.componentList
     }
@@ -82,6 +94,10 @@ export const useChartEditStoreStore = defineStore({
     setTargetSelectChart(selectId?:TargetChartType["selectId"]) {
       this.targetChart.selectId = selectId
     },
+    // * 设置记录数据
+    setRecordCharts(item: CreateComponentType | CreateComponentType[] | undefined) {
+      this.recordCharts = cloneDeep(item)
+    },
     // * 找到目标 id 数据下标位置
     fetchTargetIndex(): number {
       const index = this.componentList.findIndex(e => e.id === this.getTargetChart.selectId)
@@ -91,8 +107,17 @@ export const useChartEditStoreStore = defineStore({
       }
       return index
     },
-    // * 新增组件列表
-    addComponentList(chartData: CreateComponentType, isEnd = false): void {
+    /**
+     * * 新增组件列表
+     * @param chartData 新图表实例
+     * @param isEnd 是否末端插入
+     * @param isHistory 是否进行记录
+     * @returns 
+     */
+    addComponentList(chartData: CreateComponentType, isEnd = false, isHistory = false): void {
+      if(isHistory) {
+        chartHistoryStoreStore.createAddHistory(chartData)
+      }
       if(isEnd) {
         this.componentList.unshift(chartData)
         return
@@ -105,6 +130,7 @@ export const useChartEditStoreStore = defineStore({
         loadingStart()
         const index  = this.fetchTargetIndex()
         if (index !== -1) {
+          chartHistoryStoreStore.createDeleteHistory(this.getComponentList[index])
           this.componentList.splice(index, 1)
           loadingFinish()
           return
@@ -118,6 +144,16 @@ export const useChartEditStoreStore = defineStore({
       if(index < 1 && index > this.getComponentList.length) return
       this.componentList[index] = newData
     },
+    // * 设置页面样式属性
+    setPageStyle<T extends keyof CSSStyleDeclaration>(
+      key: T,
+      value: any
+    ): void {
+      const dom = this.getEditCanvas.editContentDom
+      if (dom) {
+        dom.style[key] = value
+      }
+    },
     // * 移动组件列表位置到两端
     setBothEnds(isEnd = false): void {
       try {
@@ -136,6 +172,7 @@ export const useChartEditStoreStore = defineStore({
             return
           }
           // 插入两端
+          chartHistoryStoreStore.createLaryerHistory(this.getComponentList[index])
           this.addComponentList(this.getComponentList[index], isEnd)
           this.getComponentList.splice(isEnd ? index + 1: index, 1)
           loadingFinish()
@@ -153,16 +190,6 @@ export const useChartEditStoreStore = defineStore({
     setBottom(): void {
       this.setBothEnds(true)
     },
-    // * 设置页面样式属性
-    setPageStyle<T extends keyof CSSStyleDeclaration>(
-      key: T,
-      value: any
-    ): void {
-      const dom = this.getEditCanvas.editContentDom
-      if (dom) {
-        dom.style[key] = value
-      }
-    },
     // * 互换图表位置
     wrap(isDown = false) {
       try {
@@ -186,8 +213,10 @@ export const useChartEditStoreStore = defineStore({
           const targetItem = this.getComponentList[index]
           const swapItem = this.getComponentList[swapIndex]
           
+          chartHistoryStoreStore.createLaryerHistory(targetItem)
           this.updateComponentList(index, swapItem)
           this.updateComponentList(swapIndex, targetItem)
+
           loadingFinish()
           return
         }
@@ -203,6 +232,57 @@ export const useChartEditStoreStore = defineStore({
     setDown() {
       this.wrap(true)
     },
+    // * 复制
+    setCopy() {
+      try {
+        loadingStart()
+        const index:number  = this.fetchTargetIndex()
+        if (index !== -1) {
+          this.setRecordCharts(this.getComponentList[index])
+          window['$message'].success('复制成功!')
+          loadingFinish()
+        }
+      } catch(value) {
+        loadingError()
+      }
+    },
+    // * 粘贴
+    setParse() {
+      try {
+        loadingStart()
+        const recordCharts = this.getRecordCharts
+        if (recordCharts === undefined) {
+          loadingFinish()
+          return
+        }
+        const parseHandle = (e: CreateComponentType) => {
+          e = cloneDeep(e)
+          // 生成新 id
+          e.id = getUUID()
+          // 偏移位置
+          e.attr.x = e.attr.x + 30
+          e.attr.y = e.attr.y + 30
+          return e
+        }
+        if (Array.isArray(recordCharts)) {
+          recordCharts.forEach((e: CreateComponentType) => {
+            console.log(parseHandle(e));
+            this.addComponentList(parseHandle(e), undefined, true)
+          })
+          loadingFinish()
+          return
+        }
+        this.addComponentList(parseHandle(recordCharts), undefined, true)
+        loadingFinish()
+      } catch(value) { 
+        loadingError()
+      }
+    },
+    // * 设置鼠标位置
+    setMousePosition(x: number, y: number): void {
+      this.mousePosition.x = x
+      this.mousePosition.y = y
+    },
     // * 设置页面变换时候的 Class
     setPageSizeClass(): void {
       const dom = this.getEditCanvas.editContentDom
@@ -218,11 +298,6 @@ export const useChartEditStoreStore = defineStore({
       this.setPageStyle('height', `${this.getEditCanvas.height}px`)
       this.setPageStyle('width', `${this.getEditCanvas.width}px`)
     },
-    // * 设置鼠标位置
-    setMousePosition(x: number, y: number): void {
-      this.mousePosition.x = x
-      this.mousePosition.y = y
-    },
     // * 计算缩放
     computedScale() {
       if (this.getEditCanvas.editLayoutDom) {

+ 16 - 0
src/store/modules/chartHistoryStore/chartHistoryDefine.ts

@@ -0,0 +1,16 @@
+import {
+  HistoryTargetTypeEnum,
+  HistoryActionTypeEnum
+} from './chartHistoryStore.d'
+
+export const historyActionTypeName = {
+  [HistoryActionTypeEnum.ADD]: '新增图表',
+  [HistoryActionTypeEnum.DELETE]: '删除图表',
+  [HistoryActionTypeEnum.UPDATE]: '修改属性',
+  [HistoryActionTypeEnum.MOVE]: '移动图表',
+  [HistoryActionTypeEnum.PASTE]: '粘贴图表',
+  [HistoryActionTypeEnum.LARYER]: '改变层级',
+  [HistoryActionTypeEnum.SELECT_HISTORY]: '选择记录',
+  
+  [HistoryTargetTypeEnum.CANVAS]: '画布初始化'
+}

+ 36 - 8
src/store/modules/chartHistoryStore/chartHistoryStore.d.ts

@@ -1,28 +1,56 @@
 import { CreateComponentType } from '@/packages/index.d'
+import { ChartLayoutType } from '@/store/modules/chartLayoutStore/chartLayoutStore.d'
 
 // 操作类型枚举
-export enum HistoryTypeEnum {
+export enum HistoryActionTypeEnum {
+  // 新增
   ADD = 'add',
+  // 删除
   DELETE = 'delete',
+  // 更新(位置,属性)
+  UPDATE = 'update',
+  // 移动
   MOVE = 'move',
+  // 粘贴
+  PASTE = 'paste',
+  // 改变层级
+  LARYER = 'laryer',
+  // 选择历史记录
   SELECT_HISTORY = 'selectHistory'
 }
 
+// 对象类型
+export enum HistoryTargetTypeEnum {
+  CANVAS = 'canvas',
+  CHART = 'chart'
+}
+
 // 历史栈
 export enum HistoryStackEnum {
-  BACK_STACK= 'backStack',
-  FORWARD_STACK= 'forwardStack',
+  BACK_STACK = 'backStack',
+  FORWARD_STACK = 'forwardStack'
+}
+
+// 历史记录项
+export enum HistoryStackItemEnum {
+  ID = 'id',
+  TARGET_TYPE = 'targetType',
+  ACTION_TYPE = 'actionType',
+  HISTORY_DATA = 'historyData'
 }
 
 // 历史记录项类型
-export interface HistoryItemType extends  CreateComponentType {
-  historyType: HistoryTypeEnum
+export interface HistoryItemType {
+  [HistoryStackItemEnum.ID]: string
+  [HistoryStackItemEnum.TARGET_TYPE]: HistoryTargetTypeEnum
+  [HistoryStackItemEnum.ACTION_TYPE]: HistoryActionTypeEnum
+  [HistoryStackItemEnum.HISTORY_DATA]: CreateComponentType | ChartLayoutType
 }
 
 // 历史 Store 类型
 export interface ChartHistoryStoreType {
   // 后退栈
-  [HistoryStackEnum.BACK_STACK]: Array<HistoryItemType>,
+  [HistoryStackEnum.BACK_STACK]: Array<HistoryItemType>
   // 前进栈
-  [HistoryStackEnum.FORWARD_STACK]: Array<HistoryItemType>,
-}
+  [HistoryStackEnum.FORWARD_STACK]: Array<HistoryItemType>
+}

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

@@ -0,0 +1,105 @@
+import { defineStore } from 'pinia'
+import { CreateComponentType } from '@/packages/index.d'
+import { ChartLayoutType } from '@/store/modules/chartLayoutStore/chartLayoutStore.d'
+import { useChartLayoutStore } from '@/store/modules/chartLayoutStore/chartLayoutStore'
+import {
+  HistoryStackEnum,
+  HistoryStackItemEnum,
+  HistoryActionTypeEnum,
+  HistoryTargetTypeEnum,
+  HistoryItemType,
+  ChartHistoryStoreType
+} from './chartHistoryStore.d'
+
+export const useChartHistoryStoreStore = defineStore({
+  id: 'useChartHistoryStore',
+  state: (): ChartHistoryStoreType => ({
+    // 后退栈(记录栈)
+    backStack: [],
+    // 前进栈
+    forwardStack: []
+  }),
+  getters: {
+    getBackStack(): Array<HistoryItemType> {
+      return this.backStack
+    },
+    getForwardStack(): Array<HistoryItemType> {
+      return this.forwardStack
+    }
+  },
+  actions: {
+    /**
+     * * 新增记录并插入栈
+     * @param item 图表实例
+     * @param actionType 动作类型
+     * @param targetType 对象类型(默认图表)
+     */
+    createStackItem(item: CreateComponentType | ChartLayoutType, actionType: HistoryActionTypeEnum, targetType: HistoryTargetTypeEnum = HistoryTargetTypeEnum.CHART) {
+      this.pushBackStackItem({
+        [HistoryStackItemEnum.ID]: new Date().getTime().toString(),
+        [HistoryStackItemEnum.HISTORY_DATA]: item,
+        [HistoryStackItemEnum.ACTION_TYPE]: actionType,
+        [HistoryStackItemEnum.TARGET_TYPE]: targetType,
+      })
+    },
+    // * 画布初始化
+    canvasInit(canvas: ChartLayoutType) {
+      this.createStackItem(canvas, HistoryActionTypeEnum.ADD, HistoryTargetTypeEnum.CANVAS)
+    },
+    // * 推入记录栈
+    pushBackStackItem(item: HistoryItemType | Array<HistoryItemType>): void {
+      if (item instanceof Array) this.backStack = [...this.backStack, ...item]
+      else this.backStack.push(item)
+    },
+    // * 推入前进栈
+    pushForwardStack(item: HistoryItemType | Array<HistoryItemType>): void {
+      if (item instanceof Array)
+        this.forwardStack = [...this.forwardStack, ...item]
+      else this.forwardStack.push(item)
+    },
+    // * 移出记录栈
+    popBackStackItem( index?: number ): HistoryItemType[] | HistoryItemType | undefined {
+      const length = this.backStack.length
+      if (index && length >= index) {
+        return this.backStack.splice(-index)
+      }
+      if (this.backStack.length > 0) {
+        return this.backStack.pop()
+      }
+    },
+    // * 移出前进栈
+    popForwardStack( index?: number ): HistoryItemType[] | HistoryItemType | undefined {
+      const length = this.forwardStack.length
+      if (index && length >= index) {
+        return this.forwardStack.splice(-index)
+      }
+      if (this.forwardStack.length > 0) {
+        return this.forwardStack.pop()
+      }
+    },
+    // * 新增组件记录
+    createAddHistory(item: CreateComponentType) {
+      this.createStackItem(item, HistoryActionTypeEnum.ADD, HistoryTargetTypeEnum.CHART)
+    },
+    // * 更新属性记录(大小、图表属性)
+    createUpdateHistory(item: CreateComponentType) {
+      this.createStackItem(item, HistoryActionTypeEnum.UPDATE, HistoryTargetTypeEnum.CHART)
+    },
+    // * 删除组件记录
+    createDeleteHistory(item: CreateComponentType) {
+      this.createStackItem(item, HistoryActionTypeEnum.DELETE, HistoryTargetTypeEnum.CHART)
+    },
+    // * 移动组件记录
+    createMoveHistory(item: CreateComponentType) {
+      this.createStackItem(item, HistoryActionTypeEnum.MOVE, HistoryTargetTypeEnum.CHART)
+    },
+    // * 改变层级组件记录
+    createLaryerHistory(item: CreateComponentType) {
+      this.createStackItem(item, HistoryActionTypeEnum.LARYER, HistoryTargetTypeEnum.CHART)
+    },
+    // * 粘贴组件记录
+    createPasteHistory(item: CreateComponentType) {
+      this.createStackItem(item, HistoryActionTypeEnum.PASTE, HistoryTargetTypeEnum.CHART)
+    },
+  }
+})

+ 0 - 62
src/store/modules/chartHistoryStore/chartHistoryStore}.ts

@@ -1,62 +0,0 @@
-import { defineStore } from 'pinia'
-import {
-  HistoryStackEnum,
-  HistoryItemType,
-  ChartHistoryStoreType
-} from './chartHistoryStore.d'
-import { setLocalStorage, getLocalStorage } from '@/utils'
-import { StorageEnum } from '@/enums/storageEnum'
-
-export const useChartHistoryStoreStore = defineStore({
-  id: 'useChartHistoryStore',
-  state: (): ChartHistoryStoreType => ({
-    // 后退栈(记录栈)
-    backStack: [],
-    // 前进栈
-    forwardStack: []
-  }),
-  getters: {
-    getBackStack(): Array<HistoryItemType> {
-      return this.backStack
-    },
-    getForwardStack(): Array<HistoryItemType> {
-      return this.forwardStack
-    }
-  },
-  actions: {
-    // * 推入记录栈
-    addBackStackItem(item: HistoryItemType | Array<HistoryItemType>): void {
-      if(item instanceof Array) this.backStack = [...this.backStack, ...item]
-      else this.backStack.push(item)
-    },
-    // * 推入前进栈
-    addForwardStack(item: HistoryItemType | Array<HistoryItemType>): void {
-      if(item instanceof Array) this.forwardStack = [...this.forwardStack, ...item]
-      else this.forwardStack.push(item)
-    },
-    // * 移出记录栈
-    popBackStackItem(
-      index?: number
-    ): HistoryItemType[] | HistoryItemType | undefined {
-      const length = this.backStack.length
-      if (index && length >= index) {
-        return this.backStack.splice(-index)
-      }
-      if (this.backStack.length > 0) {
-        return this.backStack.pop()
-      }
-    },
-    // * 移出前进栈
-    popForwardStack(
-      index?: number
-    ): HistoryItemType[] | HistoryItemType | undefined {
-      const length = this.forwardStack.length
-      if (index && length >= index) {
-        return this.forwardStack.splice(-index)
-      }
-      if (this.forwardStack.length > 0) {
-        return this.forwardStack.pop()
-      }
-    }
-  }
-})

+ 1 - 3
src/store/modules/chartLayoutStore/chartLayoutStore.ts

@@ -8,9 +8,7 @@ const chartEditStore = useChartEditStoreStore()
 
 const { GO_CHART_LAYOUT_STORE } = StorageEnum
 
-const storageChartLayout: ChartLayoutType = getLocalStorage(
-  GO_CHART_LAYOUT_STORE
-)
+const storageChartLayout: ChartLayoutType = getLocalStorage( GO_CHART_LAYOUT_STORE)
 
 // 编辑区域布局和静态设置
 export const useChartLayoutStore = defineStore({

+ 7 - 1
src/styles/common/style.scss

@@ -18,6 +18,12 @@
   text-align: center;
 }
 
+.go-flex-items-center {
+  display: flex;
+  align-items: center;
+  text-align: center;
+}
+
 .go-flex-no-wrap {
   flex-wrap: nowrap !important;
 }
@@ -108,4 +114,4 @@
 .go-px-0 {
   @extend .go-pl-0;
   @extend .go-pr-0;
-}
+}

+ 1 - 2
src/utils/page.ts

@@ -3,7 +3,6 @@ import { ResultEnum } from '@/enums/httpEnum'
 import { ErrorPageNameMap, PageEnum } from '@/enums/pageEnum'
 import router from '@/router'
 import { docPath, giteeSourceCodePath } from '@/settings/pathConst'
-import { goDialog } from '@/utils/plugin'
 
 /**
  * * 根据名字跳转路由
@@ -139,6 +138,6 @@ export const fetchRouteParams = () => {
  * * 回到主页面
  * @param confirm
  */
-export const goHome = <T extends typeof goDialog>(confirm: boolean, params: T) => {
+export const goHome = () => {
   routerTurnByName(PageEnum.BASE_HOME_NAME)
 }

+ 14 - 8
src/utils/utils.ts

@@ -1,7 +1,7 @@
 import { h } from 'vue'
 import { NIcon } from 'naive-ui'
 import screenfull from 'screenfull'
-import debounce from 'lodash/debounce'
+import throttle from 'lodash/throttle'
 
 /**
  * * 生成一个不重复的ID
@@ -91,12 +91,18 @@ export const setDomAttribute = <K extends keyof CSSStyleDeclaration, V extends C
     HTMLElement.style[key] = value
   }
 }
-
+/**
+ * * 判断是否是 mac
+ * @returns boolean
+ */
+export const isMac = () => {
+  return /macintosh|mac os x/i.test(navigator.userAgent)
+}
 /**
  * * 挂载监听
  */
-export const goAddEventListener = <K extends keyof WindowEventMap>(
-  target: EventTarget,
+export const addEventListener = <K extends keyof WindowEventMap>(
+  target: HTMLElement | Document,
   type: K,
   listener: any,
   options?: boolean | AddEventListenerOptions | undefined
@@ -104,7 +110,7 @@ export const goAddEventListener = <K extends keyof WindowEventMap>(
   if (!target) return
   target.addEventListener(
     type,
-    debounce(listener, 300, {
+    throttle(listener, 300, {
       leading: true,
       trailing: false
     }),
@@ -115,10 +121,10 @@ export const goAddEventListener = <K extends keyof WindowEventMap>(
 /**
  * * 卸载监听
  */
-export const goRemoveEventListener = <K extends keyof WindowEventMap>(
-  target: EventTarget,
+export const removeEventListener = <K extends keyof WindowEventMap>(
+  target: HTMLElement | Document,
   type: K,
-  listener: EventListenerOrEventListenerObject
+  listener: any
 ) => {
   if (!target) return
   target.removeEventListener(type, listener)

+ 27 - 12
src/views/chart/ContentEdit/components/EditBottom/index.vue

@@ -1,19 +1,17 @@
 <template>
   <div class="go-edit-bottom">
-    <n-space>
-      <n-text>
-        滤镜设置
-      </n-text>
+    <History />
+
+    <n-space class="bottom-ri">
       <!-- 快捷键提示 -->
       <n-popselect :options="shortcutKeyOptions" size="medium">
-        <n-button class="scale-btn" secondary size="mini">
-          <n-icon class="lock-icon" size="18" :depth="3">
+        <n-button class="scale-btn" quaternary size="mini">
+          <n-icon class="lock-icon" size="18" :depth="2">
             <DicomOverlayIcon />
           </n-icon>
         </n-button>
       </n-popselect>
-    </n-space>
-    <n-space class="bottom-ri">
+
       <!-- 缩放比例 -->
       <n-select
         :disabled="lockScale"
@@ -62,13 +60,14 @@
 <script setup lang="ts">
 import { reactive, ref, toRefs, watchEffect } from 'vue'
 import { icon } from '@/plugins'
+import { History } from '../History/index'
+import { useDesignStore } from '@/store/modules/designStore/designStore'
 const { LockClosedOutlineIcon, LockOpenOutlineIcon } = icon.ionicons5
 const { DicomOverlayIcon } = icon.carbon
 import {
   getChartEditStore,
   getChartEditStoreEnum
 } from '../../hooks/useStore.hook'
-import { useDesignStore } from '@/store/modules/designStore/designStore'
 
 // 全局颜色
 const designStore = useDesignStore()
@@ -141,9 +140,25 @@ const shortcutKeyOptions = [
     value: '1'
   },
   {
-    label: 'Ctrl + C 复制',
+    label: 'Delete 删除',
     value: '2'
-  }
+  },
+  {
+    label: 'Ctrl + C 复制',
+    value: '3'
+  },
+  {
+    label: 'Ctrl + X 剪切',
+    value: '4'
+  },
+  {
+    label: 'Ctrl + Z 后退',
+    value: '5'
+  },
+  {
+    label: 'Ctrl + Shift + Z 前进',
+    value: '6'
+  },
 ]
 
 // 监听 scale 变化
@@ -157,7 +172,7 @@ watchEffect(() => {
 <style lang="scss" scoped>
 @include go(edit-bottom) {
   width: 100%;
-  padding: 0 20px;
+  padding: 0 10px;
   display: flex;
   align-items: center;
   justify-content: space-between;

+ 3 - 0
src/views/chart/ContentEdit/components/History/index.ts

@@ -0,0 +1,3 @@
+import History from './index.vue'
+
+export { History }

+ 129 - 0
src/views/chart/ContentEdit/components/History/index.vue

@@ -0,0 +1,129 @@
+<template>
+  <div class="go-edit-history go-flex-items-center">
+    <n-dropdown
+      @select="handleSelect"
+      :show="showDropdownRef"
+      :options="options"
+      scrollable
+      size="small"
+      placement="top-start"
+      style="max-height: 100vh; overflow-y: auto;"
+    >
+      <n-button
+        class="mr-10"
+        secondary
+        size="small"
+        :disabled="options.length === 0"
+        @click="handleClick"
+      >
+        <span class="btn-text">历史记录</span>
+        <!-- <n-icon class="lock-icon" size="18" :depth="2">
+          <TimeOutlineIcon />
+        </n-icon> -->
+      </n-button>
+    </n-dropdown>
+
+    <n-tooltip trigger="hover">
+      <template #trigger>
+        <n-icon size="21" :depth="3">
+          <HelpOutlineIcon />
+        </n-icon>
+      </template>
+      <span>最多只保留 20 条记录</span>
+    </n-tooltip>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { icon } from '@/plugins'
+import { renderIcon } from '@/utils'
+import { useChartHistoryStoreStore } from '@/store/modules/chartHistoryStore/chartHistoryStore'
+import { historyActionTypeName } from '@/store/modules/chartHistoryStore/chartHistoryDefine'
+import { CreateComponentType } from '@/packages/index.d'
+import {
+  HistoryItemType,
+  HistoryTargetTypeEnum,
+  HistoryActionTypeEnum
+} from '@/store/modules/chartHistoryStore/chartHistoryStore.d'
+
+const {
+  TimeOutlineIcon,
+  DesktopOutlineIcon,
+  PencilIcon,
+  TrashIcon,
+  CopyIcon,
+  LayersIcon,
+  DuplicateIcon,
+  HelpOutlineIcon
+} = icon.ionicons5
+const { StackedMoveIcon } = icon.carbon
+const showDropdownRef = ref(false)
+
+const chartHistoryStoreStore = useChartHistoryStoreStore()
+
+// 设置类型对应图标
+const iconHandle = (e: HistoryItemType) => {
+  // 画布编辑
+  if (e.targetType === HistoryTargetTypeEnum.CANVAS) {
+    return renderIcon(DesktopOutlineIcon)
+  }
+  switch (e.actionType) {
+    case HistoryActionTypeEnum.UPDATE:
+      return renderIcon(PencilIcon)
+    case HistoryActionTypeEnum.DELETE:
+      return renderIcon(TrashIcon)
+    case HistoryActionTypeEnum.PASTE:
+      return renderIcon(CopyIcon)
+    case HistoryActionTypeEnum.LARYER:
+      return renderIcon(LayersIcon)
+    case HistoryActionTypeEnum.MOVE:
+      return renderIcon(StackedMoveIcon)
+    case HistoryActionTypeEnum.ADD:
+      return renderIcon(DuplicateIcon)
+    default:
+      return renderIcon(PencilIcon)
+  }
+}
+
+// 设置类型对应文本
+const labelHandle = (e: HistoryItemType) => {
+  // 画布编辑
+  if (e.targetType === HistoryTargetTypeEnum.CANVAS) {
+    return historyActionTypeName[HistoryTargetTypeEnum.CANVAS]
+  }
+  return `${historyActionTypeName[e.actionType]} - ${
+    (e.historyData as CreateComponentType).chartData.title
+  }`
+}
+
+const options = computed(() => {
+  const backStack: HistoryItemType[] = chartHistoryStoreStore.getBackStack
+  const options = backStack.map((e: HistoryItemType) => {
+    return {
+      label: labelHandle(e),
+      key: e.id,
+      icon: iconHandle(e)
+    }
+  })
+  return options
+})
+
+const handleClick = () => {
+  showDropdownRef.value = !showDropdownRef.value
+}
+const handleSelect = (key: string) => {}
+</script>
+
+<style lang="scss" scoped>
+@include go(edit-history) {
+  max-height: 50vh;
+  .mr-10 {
+    margin-right: 10px;
+  }
+  .btn-text {
+    font-size: 12px;
+    margin-right: 3px;
+  }
+}
+</style>

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

@@ -29,7 +29,7 @@ export const handleDrop = async (e: DragEvent) => {
     let newComponent:CreateComponentType = await createComponent(dropData)
 
     newComponent.setPosition(e.offsetX - newComponent.attr.w / 2, e.offsetY - newComponent.attr.h / 2)
-    chartEditStore.addComponentList(newComponent)
+    chartEditStore.addComponentList(newComponent, false, true)
     chartEditStore.setTargetSelectChart(newComponent.id) 
     loadingFinish()
   } catch (error) {

+ 36 - 0
src/views/chart/ContentEdit/hooks/useKeyboard.hook.ts

@@ -0,0 +1,36 @@
+import { isMac, addEventListener, removeEventListener } from '@/utils'
+import { getChartEditStore } from './useStore.hook'
+
+const chartEditStore = getChartEditStore()
+
+const KeyboardHandle = (e: KeyboardEvent) => {
+  const ismacRes = isMac()
+
+  // 暂不支持mac,因为我没有😤👻
+  if(ismacRes) return
+  const key = e.key.toLowerCase()
+
+  if (key === 'delete') {
+    chartEditStore.removeComponentList()
+    return
+  }
+  if (e.ctrlKey) {
+    switch (key) {
+      // 复制
+      case 'c': chartEditStore.setCopy()
+        break;
+      // 粘贴
+      case 'v': chartEditStore.setParse()
+        break;
+    }
+    e.preventDefault()
+  }
+}
+
+export const useAddKeyboard = () => {
+  addEventListener(document, 'keyup', KeyboardHandle)
+}
+
+export const useRemoveKeyboard = () => {
+  removeEventListener(document, 'keyup', KeyboardHandle)
+}

+ 7 - 0
src/views/chart/ContentEdit/index.vue

@@ -50,6 +50,7 @@ import { EditBottom } from './components/EditBottom'
 import { ShapeBox } from './components/ShapeBox/index'
 
 import { useLayout } from './hooks/useLayout.hook'
+import { useAddKeyboard, useRemoveKeyboard } from './hooks/useKeyboard.hook'
 import { handleDrop, handleDragOver, useMouseHandle } from './hooks/useDrop.hook'
 import { useContextMenu } from '@/views/chart/hooks/useContextMenu.hook'
 import { getChartEditStore } from './hooks/useStore.hook'
@@ -66,6 +67,12 @@ useLayout()
 // 点击事件
 const editRangeRef = ref<HTMLElement | null>(null)
 const { mouseenterHandle, mouseleaveHandle, mousedownHandle } = useMouseHandle()
+
+// 键盘事件
+onMounted(() => {
+  useAddKeyboard()
+})
+
 </script>
 
 <style lang="scss" scoped>

+ 5 - 1
src/views/chart/HeaderLeftBtn/index.vue

@@ -27,6 +27,7 @@ import { icon } from '@/plugins'
 const { LayersIcon, BarChartIcon, PrismIcon, HomeIcon } = icon.ionicons5
 import { useChartLayoutStore } from '@/store/modules/chartLayoutStore/chartLayoutStore'
 import { ChartLayoutStoreEnum } from '@/store/modules/chartLayoutStore/chartLayoutStore.d'
+import { useRemoveKeyboard } from '../ContentEdit/hooks/useKeyboard.hook'
 
 const { setItem } = useChartLayoutStore()
 const { getLayers, getCharts, getDetails } = toRefs(useChartLayoutStore())
@@ -76,7 +77,10 @@ const goHomeHandle = () => {
   goDialog({
     message: '返回将不会保存任何操作',
     isMaskClosable: true,
-    onPositiveCallback: goHome
+    onPositiveCallback: () => {
+      goHome()
+      useRemoveKeyboard()
+    }
   })
 }
 </script>

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

@@ -4,7 +4,7 @@ import { CreateComponentType } from '@/packages/index.d'
 import { renderIcon, loadingError } from '@/utils'
 import { icon } from '@/plugins'
 
-const { CopyIcon, TrashIcon, ChevronDownIcon, ChevronUpIcon } = icon.ionicons5
+const { CopyIcon, ClipboardOutlineIcon, TrashIcon, ChevronDownIcon, ChevronUpIcon } = icon.ionicons5
 const { UpToTopIcon, DownToBottomIcon } = icon.carbon
 
 const chartEditStore = useChartEditStoreStore()
@@ -12,6 +12,7 @@ const chartEditStore = useChartEditStoreStore()
 enum MenuEnum {
   DELETE = 'delete',
   COPY = 'copy',
+  PARSE = 'parse',
   TOP = 'top',
   BOTTOM = 'bottom',
   UP = 'up',
@@ -32,7 +33,13 @@ const defaultOptions: MenuOptionsItemType[] = [
     label: '复制',
     key: MenuEnum.COPY,
     icon: renderIcon(CopyIcon),
-    fnHandle: () => {}
+    fnHandle: chartEditStore.setCopy
+  },
+  {
+    label: '粘贴',
+    key: MenuEnum.PARSE,
+    icon: renderIcon(ClipboardOutlineIcon),
+    fnHandle: chartEditStore.setParse
   },
   {
     type: 'divider',

+ 9 - 3
src/views/chart/index.vue

@@ -39,6 +39,14 @@ import { loadAsyncComponent } from '@/utils'
 import { HeaderPro } from '@/layout/components/HeaderPro'
 import { useContextMenu } from './hooks/useContextMenu.hook'
 import { useChartEditStoreStore } from '@/store/modules/chartEditStore/chartEditStore'
+
+import { useChartLayoutStore } from '@/store/modules/chartLayoutStore/chartLayoutStore'
+import { useChartHistoryStoreStore } from '@/store/modules/chartHistoryStore/chartHistoryStore'
+const chartLayoutStore = useChartLayoutStore()
+const chartHistoryStoreStore = useChartHistoryStoreStore()
+// 记录初始化
+chartHistoryStoreStore.canvasInit(chartLayoutStore)
+
 const chartEditStore = useChartEditStoreStore()
 
 const HeaderLeftBtn = loadAsyncComponent(() =>
@@ -47,9 +55,7 @@ const HeaderLeftBtn = loadAsyncComponent(() =>
 const HeaderRightBtn = loadAsyncComponent(() =>
   import('./HeaderRightBtn/index.vue')
 )
-const HeaderTitle = loadAsyncComponent(() =>
-  import('./HeaderTitle/index.vue')
-)
+const HeaderTitle = loadAsyncComponent(() => import('./HeaderTitle/index.vue'))
 const ContentLayers = loadAsyncComponent(() =>
   import('./ContentLayers/index.vue')
 )