element-ui element-plus dialog - 分析

源代码地址 - dialog

version:element-plus 1.0.1-beta.0

index.vue

<template>
  <!-- dialog是v-if控制渲染的 -->
  <template v-if="destroyOnClose && !visible"></template>
  <template v-else>
    <!-- vue3 新增组件 teleport  to表示元素出现在他的子元素中 -->
    <!-- 个人理解就是 to的目标就是 调用appendChild -->
    <teleport to="body" :disabled="!appendToBody">
      <transition
        name="dialog-fade"
        @after-enter="afterEnter"
        @after-leave="afterLeave"
      >
        <el-overlay
          v-if="visible"
          :z-index="zIndex"
          :mask="modal"
          @click="onModalClick"
        >
          <!-- $event.stopPropagation() 是防止冒泡给父标签mask 他点击就会触发onModalClick:关闭dialog -->
          <div
            ref="dialogRef"
            v-trap-focus
            :class="[
              'el-dialog',
              {
                'is-fullscreen': fullscreen,
                'el-dialog--center': center,
              },
              customClass,
            ]"
            aria-modal="true"
            role="dialog"
            :aria-label="title || 'dialog'"
            :style="style"
            @click="$event.stopPropagation()"
          >
            <div class="el-dialog__header">
              <!-- dialog 头部插槽 -->
              <slot name="header">
                <span class="el-dialog__title">
                  {{ title }}
                </span>
              </slot>
              <button
                aria-label="close"
                class="el-dialog__headerbtn"
                type="button"
                @click="handleClose"
              >
                <i class="el-dialog__close el-icon el-icon-close"></i>
              </button>
            </div>
            <div class="el-dialog__body">
              <!-- body就是默认插槽 -->
              <slot></slot>
            </div>
            <!-- footer插槽 -->
            <div v-if="$slots.footer" class="el-dialog__footer">
              <slot name="footer"></slot>
            </div>
          </div>
        </el-overlay>
      </transition>
    </teleport>
  </template>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
// https://blog.csdn.net/qq_33221861/article/details/111477433
import { TrapFocus } from '@element-plus/directives'
// 判断是不是 ['px', 'rem', 'em', 'vw', '%', 'vmin', 'vmax']这些结尾的
import { isValidWidthUnit } from '@element-plus/utils/validators'

// https://blog.csdn.net/qq_33221861/article/details/111475961
import { Overlay } from '@element-plus/overlay'
import {
  default as useDialog,
  CLOSE_EVENT,
  CLOSED_EVENT,
  OPEN_EVENT,
  OPENED_EVENT,
  UPDATE_MODEL_EVENT,
} from './useDialog'

import type { PropType, SetupContext } from 'vue'


export default defineComponent({
  name: 'ElDialog',
  components: {
    'el-overlay': Overlay,
  },
  directives: {
    TrapFocus,
  },
  props: {
    appendToBody: {
      type: Boolean,
      default: false,
    },
    // 关闭前的回调,会暂停 Dialog 的关闭 function(done),done 用于关闭 Dialog
    beforeClose: {
      type: Function as PropType<(...args: any[]) => unknown>,
    },
    // 关闭时销毁 Dialog 中的元素
    destroyOnClose: {
      type: Boolean,
      default: false,
    },
    // 是否对头部和底部采用居中布局
    center: {
      type: Boolean,
      default: false,
    },
    customClass: {
      type: String,
      default: '',
    },
    // 是否可以通过点击 modal 关闭 Dialog
    closeOnClickModal: {
      type: Boolean,
      default: true,
    },
    // 是否可以通过按下 ESC 关闭 Dialog
    closeOnPressEscape: {
      type: Boolean,
      default: true,
    },
    // 是否为全屏 Dialog
    // 就是dialog width:100%;height:100%; 填充完整个mask | body
    fullscreen: {
      type: Boolean,
      default: false,
    },
    // 是否在 Dialog 出现时将 body 滚动锁定
    lockScroll: {
      type: Boolean,
      default: true,
    },
    // 是否需要遮罩层 传给了overlay中的mask
    modal: {
      type: Boolean,
      default: true,
    },
    // 是否显示关闭按钮
    showClose: {
      type: Boolean,
      default: true,
    },
    title: {
      type: String,
      default: '',
    },
    // Dialog 打开的延时时间,单位毫秒
    openDelay: {
      type: Number,
      default: 0,
    },
    closeDelay: {
      type: Number,
      default: 0,
    },
    // Dialog CSS 中的 margin-top 值
    top: {
      type: String,
      default: '15vh',
    },
    // 是否显示 Dialog
    modelValue: {
      type: Boolean,
      required: true,
    },
    // 宽度默认一半
    width: {
      type: String,
      default: '50%',
      validator: isValidWidthUnit,
    },
    zIndex: {
      type: Number,
    },
  },
  emits: [
    OPEN_EVENT,
    OPENED_EVENT,
    CLOSE_EVENT,
    CLOSED_EVENT,
    UPDATE_MODEL_EVENT,
  ],
  setup(props, ctx) {
    return useDialog(props, ctx as SetupContext)
  },
})
</script>

useDialog.ts

import { computed, ref, watch, nextTick, onMounted } from 'vue'

import isServer from '@element-plus/utils/isServer'
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
import PopupManager from '@element-plus/utils/popup-manager'
import { clearTimer } from '@element-plus/utils/util'
import { useLockScreen, useRestoreActive, useModal } from '@element-plus/hooks'

import type { UseDialogProps } from './dialog'
import type { SetupContext } from '@vue/runtime-core'

export const CLOSE_EVENT = 'close'
export const OPEN_EVENT = 'open'
export const CLOSED_EVENT = 'closed'
export const OPENED_EVENT = 'opened'
export { UPDATE_MODEL_EVENT }

export default function(props: UseDialogProps, ctx: SetupContext) {
  const visible = ref(false)
  const closed = ref(false)
  const dialogRef = ref(null)
  const openTimer = ref<TimeoutHandle>(null)
  const closeTimer = ref<TimeoutHandle>(null)
  const zIndex = ref(props.zIndex || PopupManager.nextZIndex())
  // 暴露了没有使用
  const modalRef = ref<HTMLElement>(null)

  const style = computed(() => {
    const style = {} as CSSStyleDeclaration
    // 如果fullscreen === false
    if (!props.fullscreen) {
      style.marginTop = props.top
      // 采用props.width
      if (props.width) {
        style.width = props.width
      }
    }
    return style
  })

  function afterEnter() {
    ctx.emit(OPENED_EVENT)
  }

  function afterLeave() {
    ctx.emit(CLOSED_EVENT)
    // .sync
    ctx.emit(UPDATE_MODEL_EVENT, false)
  }

  function open() {
    clearTimer(closeTimer)
    clearTimer(openTimer)
    // 如果设置了打开的延迟并且 > 0
    if (props.openDelay && props.openDelay > 0) {
      // 设置定时器
      openTimer.value = window.setTimeout(() => {
        openTimer.value = null
        doOpen()
      }, props.openDelay)
    } else {
      doOpen()
    }
  }

  function close() {
    // if (this.willClose && !this.willClose()) return;
    clearTimer(openTimer)
    clearTimer(closeTimer)
    // 关闭延迟
    if (props.closeDelay && props.closeDelay > 0) {
      closeTimer.value = window.setTimeout(() => {
        closeTimer.value = null
        doClose()
      }, props.closeDelay)
    } else {
      doClose()
    }
  }

  /**
   * @description: handleClose 的回调
   * @param {boolean} shouldCancel 文档中的done 用于关闭 Dialog
   * @return {*}
   */
  function hide(shouldCancel: boolean) {
    if (shouldCancel) return
    closed.value = true
    visible.value = false
  }

  function handleClose() {
    // 如果 beforeClose
    if (props.beforeClose) {
      // 调用
      props.beforeClose(hide)
    } else {
      close()
    }
  }

  function onModalClick() {
    // 判断closeOnClickModal === true
    if (props.closeOnClickModal) {
      handleClose()
    }
  }

  function doOpen() {
    // ? 服务端不管?
    if (isServer) {
      return
    }

    // if (props.willOpen?.()) {
    //  return
    // }
    visible.value = true
  }

  function doClose() {
    visible.value = false
  }

  // 如果 lockScroll为true
  if (props.lockScroll) {
    // 监听visible
    // true -> 计算scrollBar + body padding-right 并赋值给body padding-right
    // false -> 恢复原生的
    useLockScreen(visible)
  }

  // 如果closeOnPressEscape为true
  if (props.closeOnPressEscape) {
    // 监听visible
    // true -> 将 handleClose push 进一个数组(这个数组中全是handleClose方法,键盘按下 esc 触发数组中的最后一个元素:方法)
    // false -> 找到这个 handleClose 在数组中的 index 并删除他
    useModal({
      handleClose,
    }, visible)
  }

  // watch visible
  // true -> 定义了一个变量 previousActive: HTMLElement = document.activeElement
  // false -> previousActive.focus()
  // 没怎么看懂这个
  useRestoreActive(visible)

  // 监听绑定的model值
  watch(() => props.modelValue, val => {
    // true
    if (val) {
      closed.value = false
      open()
      ctx.emit(OPEN_EVENT)
      // this.$el.addEventListener('scroll', this.updatePopper)
      nextTick(() => {
        if (dialogRef.value) {
          // dialogRef 存在 则滚动到最上面
          dialogRef.value.scrollTop = 0
        }
      })
    } else {
      // this.$el.removeEventListener('scroll', this.updatePopper
      close()
      if (!closed.value) {
        ctx.emit(CLOSE_EVENT)
      }
    }
  })

  onMounted(() => {
    if (props.modelValue) {
      visible.value = true
      open()
    }
  })

  return {
    afterEnter,
    afterLeave,
    handleClose,
    onModalClick,
    closed,
    dialogRef,
    style,
    modalRef,
    visible,
    zIndex,
  }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Vue中使用Element UI上传图片并预览可以按照以下步骤进行: 1. 首先,在项目中安装Element UI和axios: ``` npm install element-ui axios --save ``` 2. 在main.js中引入Element UI和axios,并使用: ```javascript import Vue from 'vue' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import axios from 'axios' Vue.use(ElementUI) Vue.prototype.$axios = axios ``` 3. 在组件中使用`<el-upload>`组件进行文件上传,并使用`<el-image>`组件进行预览: ```html <template> <div> <el-upload action="/api/upload" :on-success="handleSuccess" :on-preview="handlePreview" list-type="picture-card"> <i class="el-icon-plus"></i> </el-upload> <el-dialog :visible.sync="dialogVisible"> <el-image :src="imageUrl" :preview-src-list="previewImages"></el-image> </el-dialog> </div> </template> <script> export default { data() { return { dialogVisible: false, imageUrl: '', previewImages: [] }; }, methods: { handleSuccess(response) { if (response.status === 200) { this.imageUrl = response.data.url; this.previewImages.push(response.data.url); } }, handlePreview(file) { this.dialogVisible = true; this.imageUrl = file.url; this.previewImages = [file.url]; } } } </script> ``` 4. 在服务器端设置相应的文件上传接口,例如使用Node.js和express: ```javascript const express = require('express'); const multer = require('multer'); const app = express(); const upload = multer({ dest: 'uploads/' }); app.post('/api/upload', upload.single('file'), (req, res) => { // 处理上传文件并返回文件URL const file = req.file; const url = `http://example.com/${file.filename}`; res.send({ url }); }); app.listen(3000, () => { console.log('Server is running on port 3000'); }); ``` 上述代码实现了一个简单的上传图片并预览的功能,具体可根据实际需求进行调整和扩展。 ### 回答2: 在Vue中使用Element UI上传图片并预览可以按照以下步骤进行: 1. 首先,在项目中引入Element UI组件库,并在需要使用的组件中进行注册。 ```js import Vue from 'vue' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI) ``` 2. 创建一个上传组件,并初始化需要的数据,如图片列表和上传接口等。 ```vue <template> <div> <el-upload action="/your-upload-url" :on-success="handleSuccess" :file-list="fileList" :on-remove="handleRemove" multiple > <el-button size="small" type="primary"> 选择图片 </el-button> </el-upload> <el-image v-for="(file, index) in fileList" :key="index" :src="file.url" :preview-src-list="previewList" fit="cover" style="width: 100px; height: 100px;" ></el-image> </div> </template> <script> export default { data() { return { fileList: [], // 上传的文件列表 previewList: [] // 预览图片列表 } }, methods: { handleSuccess(response, file, fileList) { // 上传成功的回调函数 fileList[fileList.length - 1].url = URL.createObjectURL(file.raw) this.previewList = fileList.map(file => file.url) }, handleRemove(file, fileList) { // 上传文件移除的回调函数 this.previewList = fileList.map(file => file.url) } } } </script> ``` 在上述代码中,el-upload组件负责上传图片,action属性指定上传图片的接口地址,on-success属性可以监听上传成功的事件,并通过handleSuccess方法返回上传成功后的处理方式。file-list属性用于展示上传成功的文件列表,并且绑定了on-remove属性来处理上传文件的移除事件。 el-image组件用于展示预览图片,v-for指令遍历渲染fileList数组中的每张图片,通过src属性绑定图片的地址,在handleSuccess方法中将上传成功的文件通过URL.createObjectURL方法生成临时的URL作为图片的地址。同时,preview-src-list属性绑定了previewList数组,用于展示预览图片的列表。 这样,当用户选择图片并上传成功后,页面将会展示上传的图片,并提供预览功能。 ### 回答3: 使用Element UI库实现图片上传并预览的步骤如下: 1. 首先,在项目中引入Element UI和Vue插件。 ``` import Vue from 'vue'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); ``` 2. 在Vue组件中,添加一个上传组件和一个用于预览图片的容器。 ``` <template> <div> <!-- 图片上传组件 --> <el-upload class="upload-demo" action="/your-upload-api" <!-- 上传图片的接口地址 --> :on-success="handleUploadSuccess" > <el-button size="small" type="primary">点击上传</el-button> </el-upload> <!-- 图片预览容器 --> <div class="preview-container"> <img :src="imageUrl" v-if="imageUrl" alt="预览图片" /> </div> </div> </template> <script> export default { data() { return { imageUrl: '' // 预览图片的URL }; }, methods: { handleUploadSuccess(response) { // 上传成功后,将返回的图片URL赋值给imageUrl this.imageUrl = response.data.imageUrl; } } }; </script> ``` 3. 完成上述代码后,当用户选择图片并上传成功时,上传接口会返回图片的URL。通过`handleUploadSuccess`方法将返回的URL赋值给`imageUrl`,然后在预览容器中显示预览图片即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值