瀏覽代碼

撤销、重做、清除

qianduan 10 月之前
父節點
當前提交
bd53b8566b

+ 25 - 26
src/components/drag/resizable.vue

@@ -1,17 +1,16 @@
 <template>
-    <Vue3DraggableResizable classNameActive="active" :key="index" :initW="initW" :initH="initH" v-model:x="left"
-        v-model:y="top" v-model:w="w" v-model:h="h" :active="active" :handles="sticks" :draggable="true"
-        :resizable="true" @drag-start="print('drag-start')" @resize-start="print('resize-start')"
-        @resizing="print('resizing')" @dragging="print('dragging')" @drag-end="print('drag-end')"
-        @resize-end="print('resize-end')" @activated="activated" @deactivated="deactivated">
+    <Vue3DraggableResizable :snap="true" classNameActive="active" :key="dargData.soleId" :initW="initW" :initH="initH" v-model:x="left"
+        v-model:y="top" v-model:w="w" v-model:h="h" :active="active" :handles="sticks" :draggable="true" :resizable="true"
+        @drag-start="print('drag-start')" @resize-start="print('resize-start')" @resizing="print('resizing')"
+        @dragging="print('dragging')" @drag-end="print('drag-end')" @resize-end="print('resize-end')" @activated="activated"
+        @deactivated="deactivated">
         <component :is="getWidget(currentItem.type)" :config="currentItem"></component>
     </Vue3DraggableResizable>
 </template>
 
 <script lang="ts" setup>
-import getName from '../../views/widgets/getWidget';
+import getName from '../widgets/getWidget';
 import Vue3DraggableResizable from 'vue3-draggable-resizable'
-//default styles
 import 'vue3-draggable-resizable/dist/Vue3DraggableResizable.css'
 import { ref, watch } from "vue";
 const initW = ref(0)
@@ -20,7 +19,6 @@ const w = ref(0)
 const h = ref(0)
 const top = ref(0)
 const left = ref(0)
-// const minh = ref(50)
 const isResizable = ref(false)
 const sticks = ref([])
 const active = ref(true)
@@ -33,23 +31,16 @@ const props = defineProps({
     index: Number,
 })
 // 监听 props.dargData 的变化
-// watch(() => props.dargData, (newValue, oldValue) => {
-//     console.log('workOrder变化了', newValue, props.index)
-//     currentItem.value = newValue
-//     // currentItem.value = JSON.parse(JSON.stringify(newValue))
-//     initW.value = currentItem.value.css.width
-//     initH.value = currentItem.value.css.height
-//     w.value = currentItem.value.css.width
-//     h.value = currentItem.value.css.height
-//     top.value = currentItem.value.css.top
-//     left.value = currentItem.value.css.left
-//     isResizable.value = currentItem.value.config.isResizable
-//     sticks.value = currentItem.value.config.sticks
-//     active.value = currentItem.value.active
-// }, { immediate: true, deep: true })
+watch(() => currentItem.value, (newValue) => {
+    initW.value = newValue.css.width
+    initH.value = newValue.css.height
+    w.value = newValue.css.width
+    h.value = newValue.css.height
+    top.value = newValue.css.top
+    left.value = newValue.css.left
+}, { immediate: false, deep: true })
 
-currentItem.value = props.dargData
-// currentItem.value = JSON.parse(JSON.stringify(newValue))
+currentItem.value = JSON.parse(JSON.stringify(props.dargData))
 initW.value = currentItem.value.css.width
 initH.value = currentItem.value.css.height
 w.value = currentItem.value.css.width
@@ -68,14 +59,22 @@ function print(val) {
     currentItem.value.css.height = h.value
     currentItem.value.css.top = top.value
     currentItem.value.css.left = left.value
+    emit('modifiedItem', {
+        type: val,
+        item: currentItem.value,
+    });
 }
+// 组件折射
 const getWidget = (name: string) => {
-    //写的时候,组件的起名一定要与dragList中的element名字一模一样,不然会映射不上
     return getName[name]
 }
 // 激活项索引
 function activated() {
-    emit('passId', props.index);
+    const arr = {
+        index: props.index,
+        soleId: props.dargData.soleId
+    }
+    emit('passId', arr);
 }
 function deactivated() {
     console.log(active.value, '失活');

+ 0 - 0
src/views/widgets/getWidget.ts → src/components/maskedbox/index.ts


+ 8 - 0
src/components/maskedbox/layout.vue

@@ -0,0 +1,8 @@
+<template>
+    <!-- 布局组件 -->
+  <div>test</div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="scss" scoped></style>

+ 0 - 0
src/views/widgets/Images.vue → src/components/widgets/Images.vue


+ 0 - 0
src/views/widgets/Input.vue → src/components/widgets/Input.vue


+ 0 - 0
src/views/widgets/SceneButton.vue → src/components/widgets/SceneButton.vue


+ 11 - 0
src/components/widgets/getWidget.ts

@@ -0,0 +1,11 @@
+// getWidget.ts
+const gets = {} as any
+const modules = import.meta.glob('./*.vue', { eager: true })
+for (let each in modules) {
+    const name = (modules[each] as any).default.__name
+    gets[name] = (modules[each] as any).default
+}
+
+// console.log(gets);
+
+export default gets

+ 30 - 0
src/style.css

@@ -22,6 +22,13 @@ body,
     justify-content: center;
 }
 
+.center_in_column {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+}
+
 .space_between_in {
     display: flex;
     align-items: center;
@@ -35,4 +42,27 @@ body,
 .ban_incident{
     pointer-events: none !important;
     user-select: none !important;
+}
+
+.default_size{
+    font-size: 13px;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    width: auto;
+    text-align: left;
+}
+
+.title_5{
+    font-weight: 500;
+    color: rgb(51, 51, 51);
+    margin: 0px 8px 0px 0px;
+}
+
+.title_12{
+    font-size: 12px;
+}
+.w_60{
+    width: 60px;
 }

+ 181 - 65
src/views/HomeView.vue

@@ -11,10 +11,21 @@
         </div>
       </div>
       <div class="nav_center">
-        <div class="item_head_btn" v-for="(item, index) in btnOptions" :key="index">
-          <span class="iconfont icon_btn_head" :class="item.value"></span>
-          <span class="item_head_label">{{ item.label }}</span>
-        </div>
+        <button class="item_head_btn" :class="!canUndo ? 'btn_Void' : 'btn_CDK'" :disabled="!canUndo"
+          @click="operationalView('backout')">
+          <span class="iconfont icon_btn_head icon-chexiao"></span>
+          <span class="item_head_label">撤销</span>
+        </button>
+        <button class="item_head_btn" :class="!canRedo ? 'btn_Void' : 'btn_CDK'" :disabled="!canRedo"
+          @click="operationalView('renewal')">
+          <span class="iconfont icon_btn_head icon-zhongzuo"></span>
+          <span class="item_head_label">重做</span>
+        </button>
+        <button class="item_head_btn" :class="!clearFlag ? 'btn_Void' : 'btn_CDK'" :disabled="!clearFlag"
+          @click="operationalView('clear')">
+          <span class="iconfont icon_btn_head icon-qingchu"></span>
+          <span class="item_head_label">清除</span>
+        </button>
       </div>
       <div class="nav_right">
         <div class="card_right_btn" @click="preview">
@@ -52,10 +63,9 @@
         </div>
         <div class="left_paneRight">
           <el-scrollbar style="width: 100%; height: 100%" class="component-list scrollbar-wrapper">
-            <draggable :list="componentLibrary" itemKey="id" handle=".move" filter=".forbid" :force-fallback="false"
-              chosen-class="chosenClass" animation="300" @start="onStart" @end="onEnd" :group="state.groupA"
-              :fallback-class="true" :fallback-on-body="true" :touch-start-threshold="50" :fallback-tolerance="50"
-              :move="onMove" :sort="false">
+            <draggable :list="componentLibrary" itemKey="id" animation="300" @start="onStart" @end="onEnd"
+              :clone="cloneComponent" :group="{ name: 'itxst', pull: 'clone' }" :touch-start-threshold="50"
+              :fallback-tolerance="50" :sort="false">
               <template #item="{ element }">
                 <div class="item_module_left move">
                   <component :is="getWidget(element.type)" :config="element"></component>
@@ -71,12 +81,11 @@
           <img class="head_image_url" :src="imageUrl" />
           <div class="Bzu">
             <DraggableContainer>
-              <draggable class="Bzuil" v-model="extraImgs1" itemKey="id" :force-fallback="false" :sort="false"
-                :handle="'.forbid'" :fallbackOnBody="false" :group="{ name: 'undraggable', pull: false, put: true }">
+              <draggable class="Bzuil" v-model="extraImgs1" itemKey="soleId" :handle="'.forbid'" group="itxst"
+                ghostClass="ghost">
                 <template #item="{ element, index }">
                   <div class="item_module_center move">
-                    {{ index }}
-                    <VueDrag :index="index" :dargData="element" :key="index" @passId="getId"></VueDrag>
+                    <VueDrag :index="index" :dargData="element" @passId="getId" @modifiedItem="modifiedItem"></VueDrag>
                   </div>
                 </template>
               </draggable>
@@ -86,16 +95,14 @@
       </div>
       <!-- 右侧 -->
       <div class="panel_right">
-        <el-radio-group v-model="tabPosition">
-          <el-radio-button class="radio_card" value="style">样式</el-radio-button>
-          <el-radio-button class="radio_card" value="property">属性</el-radio-button>
-        </el-radio-group>
-        <div class="tabs_radio center_in" v-if="tabPosition == 'style'">
-          <el-empty description="暂无样式可调" :image-size="130" />
-        </div>
-        <div class="tabs_radio center_in" v-if="tabPosition == 'property'">
-          <el-empty description="当前无可编辑属性" :image-size="130" />
+        <!-- 样式编辑控件 -->
+        <div class="card_maskedbox">
+          <maskedbox :config="currentMove"></maskedbox>
         </div>
+        {{ currentMove }}
+        <!-- <div class="tabs_radio center_in" v-if="tabPosition == 'style'">
+          <el-empty description="暂无样式可调" :image-size="130" />
+        </div> -->
         <!-- 右侧对齐方式 -->
         <div class="card_alignment">
           <div class="bg_ment">
@@ -122,23 +129,44 @@
 import { DraggableContainer } from 'vue3-draggable-resizable'
 import Draggable from "vuedraggable";
 import moduleLibrary from "../components/moduleLibrary.vue";
-import getName from './widgets/getWidget';
-// import VueDrag from '../components/drag/index.vue'
+import getName from '../components/widgets/getWidget';
 import VueDrag from '../components/drag/resizable.vue'
+import maskedbox from './maskedbox.vue'
 import { attributeValue, layout } from '../module/index';
-import { ref } from "vue";
+import { ref, computed } from "vue";
 import { useRouter } from 'vue-router'
+import { ElMessageBox } from 'element-plus'
 
-const tabPosition = ref<string>('style');
+// const tabPosition = ref<string>('style');
 const activeName = ref<string>('bank');
 // 默认选择组件库
 const current = ref<string>('1');
 const imageUrl = new URL('@/assets/images/head.png', import.meta.url).href;
-const btnOptions = [
-  { label: "撤销", value: 'icon-chexiao' },
-  { label: "重做", value: 'icon-zhongzuo' },
-  { label: "清除", value: 'icon-qingchu' },
-];
+// 组件库
+const extraImgs = ref(attributeValue);
+const componentLibrary = JSON.parse(JSON.stringify(extraImgs.value))
+// 视图库
+const extraImgs1: any = ref([]);
+// 撤销操作的栈
+const undoStack: any = ref([]);
+// 恢复操作的栈
+const redoStack: any = ref([]);
+// 对齐方式状态
+const activate: any = ref(true);
+// 操作组件
+const currentMove: any = ref({})
+// 撤销
+const canUndo = computed(() => {
+  return undoStack.value.length > 0;
+})
+// 恢复
+const canRedo = computed(() => {
+  return redoStack.value.length > 0;
+})
+// 清除
+const clearFlag = computed(() => {
+  return extraImgs1.value.length > 0
+})
 const btnAlignment = [
   { type: 'left', label: "左对齐", value: 'icon-zuoduiqi' },
   { type: 'centerHorizontally', label: "水平居中", value: 'icon-hengxiangjuzhong' },
@@ -151,19 +179,6 @@ function getAssetsImages(name) {
   return new URL(`/src/assets/${name}`, import.meta.url).href;
 }
 
-const extraImgs = ref(attributeValue);
-const componentLibrary = JSON.parse(JSON.stringify(extraImgs.value))
-const extraImgs1: any = ref([]);
-const activate: any = ref(true);
-const state = ref({
-  message: "拖拽",
-  groupA: {
-    name: "itxst",
-    put: false, //允许拖入
-    pull: "clone"
-  },
-});
-
 //拖拽开始的事件
 const onStart = () => {
   console.log("开始拖拽");
@@ -171,15 +186,28 @@ const onStart = () => {
 
 //拖拽结束的事件
 const onEnd = (value) => {
-  console.log(value, "结束拖拽");
+  if (value.originalEvent.cancelable) {
+    const num = (375 / 2) - (currentMove.value.css.width / 2)
+    currentMove.value.css.left = num
+    currentMove.value.css.top = value.originalEvent.offsetY
+    const arr = JSON.parse(JSON.stringify(currentMove.value))
+    extraImgs1.value.push(arr)
+
+    undoStack.value.push({ type: 'add', item: arr })
+    redoStack.value = [];
+  } else {
+    currentMove.value = {}
+    extraImgs1.value.splice(value.newIndex, 1)
+  }
 };
+// 克隆组件
+const cloneComponent = (value) => {
+  currentMove.value = {
+    soleId: "box_" + new Date().getTime(),
+    ...value
+  }
+}
 
-const onMove = () => {
-  // event: MouseEvent
-  // console.log(event, 266)
-  //不允许停靠
-  return true;
-};
 const getWidget = (name: string) => {
   //组件名映射
   return getName[name]
@@ -196,31 +224,101 @@ const handleReceivedData = (num: any) => {
 function handleClick(): void {
   console.log(24);
 }
-const router = useRouter()
 
+// 操作视图按钮
+function operationalView(type: any) {
+  if (type == 'backout') {
+    // 撤销
+    const lastAction = undoStack.value.pop();
+    if (lastAction.type === 'add') {
+      const index = extraImgs1.value.findIndex((item) => item.soleId === lastAction.item.soleId);
+      if (index > -1) {
+        extraImgs1.value.splice(index, 1);
+        redoStack.value.push(lastAction);
+      }
+    } else if (lastAction.type === 'drag') {
+      // 将拖拽操作记录添加到redoStack栈
+      redoStack.value.push(lastAction);
+      lastAction.item.css.left = lastAction.startPos.left;
+      lastAction.item.css.top = lastAction.startPos.top;
+    } else if (lastAction.type === 'delete') {
+      extraImgs1.value = lastAction.item
+      redoStack.value.push(lastAction)
+    }
+  } else if (type == 'renewal') {
+    // 重做
+    const lastAction = redoStack.value.pop();
+    if (lastAction.type === 'add') {
+      extraImgs1.value.push(lastAction.item);
+      undoStack.value.push(lastAction);
+    } else if (lastAction.type === 'drag') {
+      // 将拖拽操作记录添加到undoStack栈
+      undoStack.value.push(lastAction);
+      // 还原元素拖拽后的位置
+      lastAction.item.css.left = lastAction.endPos.left;
+      lastAction.item.css.top = lastAction.endPos.top;
+    } else if (lastAction.type === 'delete') {
+      extraImgs1.value = [];
+      undoStack.value.push(lastAction);
+    }
+  } else if (type == 'clear') {
+    ElMessageBox.confirm(
+      '确定删除?',
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }
+    ).then(() => {
+      undoStack.value.push({ type: 'delete', item: extraImgs1.value })
+      redoStack.value = []
+      extraImgs1.value = []
+      currentMove.value = {}
+    }).catch((err) => {
+      console.log(err);
+    })
+  }
+}
+const router = useRouter()
 // 发布
 function announce() {
   router.push('/TestDrag')
-  console.log(extraImgs1.value, 999);
+  // console.log(extraImgs1.value, 999);
 }
-
+// 预览
 function preview() {
-  // console.log(extraImgs1.value[0], 887);
-  extraImgs1.value[0].active = true
-  extraImgs1.value = extraImgs1.value
+  console.log(extraImgs1.value, '预览');
 }
 
 // 激活项索引
 const currentIndex: any = ref(null)
+const currentSoleId: any = ref(null)
 function getId(params: any) {
-  console.log(params, 222);
+  // console.log(params, 8);
+  currentIndex.value = params.index
+  currentSoleId.value = params.soleId
+}
 
-  currentIndex.value = params
+const startX = ref('');
+const startY = ref('');
+function modifiedItem(params: any) {
+  if (params.type == 'drag-start') {
+    startX.value = params.item.css.top
+    startY.value = params.item.css.left
+  }
+  extraImgs1.value.forEach(element => {
+    if (element.soleId == params.item.soleId) {
+      element.css = params.item.css
+      if (params.type == 'drag-end') {
+        undoStack.value.push({ type: 'drag', item: params.item, startPos: { left: startY.value, top: startX.value }, endPos: { left: params.item.css.left, top: params.item.css.top } })
+        redoStack.value = [];
+      }
+    }
+  });
 }
 // 对齐方式按钮触发
 function alignment(params: any) {
-  console.log(extraImgs1.value, 222);
-
   if (currentIndex.value != null) {
     if (params.type == 'left') {
       extraImgs1.value[currentIndex.value].css.left = 0
@@ -240,6 +338,7 @@ function alignment(params: any) {
       extraImgs1.value[currentIndex.value].css.top = num
     }
   }
+  console.log(extraImgs1.value, 5);
 }
 </script>
 
@@ -298,9 +397,18 @@ function alignment(params: any) {
   flex-direction: column;
   align-items: center;
   margin: 0px 16px;
+  border: none;
+  background: none;
+  outline: none;
+  padding: 0px;
+}
+
+.btn_Void {
+  opacity: 0.5;
+  cursor: not-allowed;
 }
 
-.item_head_btn:hover .icon_btn_head {
+.btn_CDK:hover .icon_btn_head {
   color: #409EFF;
 }
 
@@ -429,8 +537,8 @@ function alignment(params: any) {
   width: 375px;
   margin: 30px auto 0px;
   height: 667px;
-  background-color: red;
-  // background-color: #fafafa;
+  // background-color: red;
+  background-color: #fafafa;
   position: relative;
   overflow: hidden;
 }
@@ -479,6 +587,11 @@ function alignment(params: any) {
   position: absolute !important;
 }
 
+.card_maskedbox {
+  padding: 20px 14px 25px;
+  overflow: hidden overlay;
+}
+
 .card_alignment {
   max-height: calc(-50px + 100vh);
   position: absolute;
@@ -575,7 +688,10 @@ img {
   // overflow: hidden;
 }
 
+.Bzu .ghost {
+  opacity: 0;
+}
+
 .Bzuil {
   height: 100%;
-}
-</style>
+}</style>

+ 155 - 0
src/views/maskedbox.vue

@@ -0,0 +1,155 @@
+<!-- 样式编辑控件 -->
+<template>
+    <div class="box_padding">
+        <div class="same_row_in">
+            <span class="default_size title_5">布局</span>
+            <el-icon>
+                <InfoFilled />
+            </el-icon>
+        </div>
+        <div class="same_row_in" style="padding: 18px 0px 13px;">
+            <div class="center_in_column">
+                <div class="dpjis"></div>
+                <div class="iUnNSJ">靠边固定</div>
+            </div>
+            <div class="center_in_column" style="margin-left: 11px;">
+                <div class="kdbebz"></div>
+                <div class="iUnNSJ">固定尺寸</div>
+            </div>
+        </div>
+    </div>
+    <div class="box_padding" v-if="config.css">
+        <div class="same_row_in">
+            <span class="default_size title_5">基础设置</span>
+            <el-icon style="cursor: pointer;" :class="basicFlag ? '' : 'putAway'" @click="getbasicSetting('basic')">
+                <ArrowDown />
+            </el-icon>
+        </div>
+        <div v-if="basicFlag" style="padding: 9px 0px 4px 18px;">
+            <div class="same_row_in" style="padding: 9px 0px;">
+                <span class="title_12 w_60 default_size">尺寸</span>
+                <div class="grey_input_bgc">
+                    <el-input-number class="card_number" v-model="config.css.width" :min="1" size="small"
+                        controls-position="right" />
+                    <span>W</span>
+                </div>
+                <div class="card_lock center_in" @click="getLock(lockFlag)">
+                    <el-icon v-if="lockFlag">
+                        <Unlock />
+                    </el-icon>
+                    <el-icon v-else>
+                        <Lock />
+                    </el-icon>
+                </div>
+                <div class="grey_input_bgc">
+                    <el-input-number class="card_number" v-model="config.css.height" :min="1" size="small"
+                        controls-position="right" />
+                    <span>H</span>
+                </div>
+            </div>
+            <div>
+
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue";
+const props = defineProps({
+    config: {
+        type: Object,
+        required: true
+    },
+})
+const basicFlag = ref(true)
+const lockFlag = ref(true)
+function getbasicSetting(params: any) {
+    console.log();
+    if (params == 'basic') {
+        if (basicFlag.value) {
+            basicFlag.value = false
+        } else {
+            basicFlag.value = true
+        }
+    }
+}
+function getLock(params: any) {
+    if (params) {
+        lockFlag.value = false
+    } else {
+        lockFlag.value = true
+    }
+}
+console.log(props.config, 888)
+</script>
+
+<style lang="scss" scoped>
+.box_padding {
+    padding: 7px 0px 4px;
+}
+
+.dpjis {
+    width: 159px;
+    height: 100px;
+    opacity: 0.9;
+    background: rgb(243, 243, 248);
+    border: 1px solid rgb(226, 226, 226);
+    border-radius: 4px;
+    position: relative;
+}
+
+.kdbebz {
+    width: 100px;
+    height: 100px;
+    opacity: 0.9;
+    background: rgb(255, 255, 255);
+    border: 1px solid rgb(226, 226, 226);
+    border-radius: 4px;
+    position: relative;
+}
+
+.iUnNSJ {
+    opacity: 0.5;
+    font-size: 12px;
+    font-weight: 400;
+    color: rgb(51, 51, 51);
+    margin-top: 8px;
+    text-align: center;
+    transform: scale(0.85);
+}
+
+.putAway {
+    transform: rotate(180deg);
+}
+
+:deep(.el-input-number.is-controls-right .el-input__wrapper) {
+    padding-left: 5px;
+    padding-right: 30px;
+}
+
+.card_number :deep(.el-input-number__decrease),
+.card_number :depp(.el-input-number__increase) {
+    width: 20px;
+}
+
+.card_lock {
+    flex: 0 0 auto;
+    cursor: pointer;
+    width: 30px;
+}
+
+.grey_input_bgc {
+    width: 85px;
+    background-color: #f1f1f7;
+    display: flex;
+    align-items: center;
+    padding: 2px;
+    border-radius: 4px;
+
+    span {
+        font-size: 12px;
+        margin: 1px;
+    }
+}
+</style>