x.naive-ui icon indicating copy to clipboard operation
x.naive-ui copied to clipboard

封装了一下modal

Open transtone opened this issue 3 months ago • 0 comments

使用 card preset 封装了一下 modal,可拖动,可以省写几个属性。

x-n-modal.vue

<template>
  <n-modal id="basic-modal" $show="showModal" :style="{ width: modalWidth }" preset="card" :size="size" :bordered="false"
           :closable="closable" :auto-focus="autoFocus" :block-scroll="blockScroll" :close-on-esc="closeOnEsc" :display-directive="displayDirective"
           :mask-closable="maskClosable" :to="to" :transform-origin="transformOrigin" :trap-focus="trapFocus" :z-index="zIndex"
           @close="emit('close')" @after-enter="$emit('after-enter')" @after-leave="$emit('after-leave')"
           @esc="$emit('esc')" @mask-click="$emit('mask-click')" @update:show="$emit('update:show', $event)">
    <template #header>
      <div id="basic-modal-bar" class="w-full" :class="{'cursor-move': draggable}">{{ title }}</div>
    </template>
    <slot name="default" />
    <template #action>
      <slot v-if="showAction" name="action">
        <n-space class="!justify-end">
          <n-button :size="size" @click="cancelModal">{{ negativeText }}</n-button>
          <n-button :size="size" type="primary" :loading="subLoading" @click="confirmModal">{{ positiveText }}</n-button>
        </n-space>
      </slot>
    </template>
  </n-modal>
</template>

<script setup>

const emit = defineEmits(['close', 'positive-click', 'update:show', 'after-leave', 'after-enter', 'mask-click', 'esc', 'negative-click'])

const showModal = defineModel('show', { type: Boolean, default: false })
const props = defineProps({
  size: { type: String },
  title: { type: String, default: '' },
  width: { type: [String, Number], default: 500 },
  draggable: { type: Boolean, default: false },
  showAction: { type: Boolean, default: true },
  negativeText: { type: String, default: '取消' },
  positiveText: { type: String, default: '确定' },
  closable: { type: Boolean, default: true },
  autoFocus: { type: Boolean, default: false },
  blockScroll: { type: Boolean, default: true },
  closeOnEsc: { type: Boolean, default: true },
  closeOnConfirm: { type: Boolean, default: true }, // 确认时不自动关闭弹窗
  closeOnCancel: { type: Boolean, default: true }, // 取消时不自动关闭弹窗
  displayDirective: { type: String, default: 'if' }, // 'if' | 'show'
  maskClosable: { type: Boolean, default: true },
  to: { type: String, default: 'body' },
  transformOrigin: { type: String, default: 'mouse' }, // 'mouse' | 'center'
  trapFocus: { type: Boolean, default: true },
  zIndex: { type: Number },
})

const modalWidth = computed(() => {
  return typeof props.width === 'number' ? `${props.width}px` : props.width
})

let subLoading = $ref(false)

watch(showModal, val => {
  if (!val) {
    subLoading = false
    return
  }
  if (!props.draggable) {return }
  nextTick(() => {
    const oBox = document.getElementById('basic-modal')
    const oBar = document.getElementById('basic-modal-bar')
    startDrag(oBar, oBox)
  })
})
const cancelModal = () => {
  emit('negative-click')
  if (props.closeOnCancel) {
    showModal.value = false
  }
}
const confirmModal = () => {
  subLoading = true
  emit('positive-click')
  if (props.closeOnConfirm) {
    showModal.value = false
  }
}
</script>

Drag.ts

//获取相关CSS属性
const getCss = function (o, key) {
  return o.currentStyle
    ? o.currentStyle[key]
    : document.defaultView?.getComputedStyle(o, null)[key]
}

const params = {
  left: 0,
  top: 0,
  currentX: 0,
  currentY: 0,
  flag: false,
}

export const startDrag = function (bar, target, callback?) {
  const screenWidth = document.body.clientWidth // body当前宽度
  const screenHeight = document.documentElement.clientHeight // 可见区域高度

  const dragDomW = target.offsetWidth // 对话框宽度
  const dragDomH = target.offsetHeight // 对话框高度

  const minDomLeft = target.offsetLeft
  const minDomTop = target.offsetTop

  const maxDragDomLeft = screenWidth - minDomLeft - dragDomW
  const maxDragDomTop = screenHeight - minDomTop - dragDomH

  if (getCss(target, 'left') !== 'auto') {
    params.left = getCss(target, 'left')
  }
  if (getCss(target, 'top') !== 'auto') {
    params.top = getCss(target, 'top')
  }

  //o是移动对象
  bar.onmousedown = function (event) {
    params.flag = true
    if (!event) {
      event = window.event
      //防止IE文字选中
      bar.onselectstart = function () {
        return false
      }
    }
    const e = event
    params.currentX = e.clientX
    params.currentY = e.clientY
  }
  document.onmouseup = function () {
    params.flag = false
    if (getCss(target, 'left') !== 'auto') {
      params.left = getCss(target, 'left')
    }
    if (getCss(target, 'top') !== 'auto') {
      params.top = getCss(target, 'top')
    }
  }
  document.onmousemove = function (event) {
    const e: any = event || window.event
    if (params.flag) {
      const nowX = e.clientX
      const nowY = e.clientY
      const disX = nowX - params.currentX
      const disY = nowY - params.currentY

      let left = parseInt(params.left) + disX
      let top = parseInt(params.top) + disY

      // 拖出屏幕边缘
      if (-left > minDomLeft) {
        left = -minDomLeft
      } else if (left > maxDragDomLeft) {
        left = maxDragDomLeft
      }

      if (-top > minDomTop) {
        top = -minDomTop
      } else if (top > maxDragDomTop) {
        top = maxDragDomTop
      }

      target.style.left = left + 'px'
      target.style.top = top + 'px'

      if (typeof callback === 'function') {
        callback((parseInt(params.left) || 0) + disX, (parseInt(params.top) || 0) + disY)
      }

      if (event.preventDefault) {
        event.preventDefault()
      }
      return false
    }
  }
}

transtone avatar Mar 31 '24 01:03 transtone