Skip to content

快速使用

结合 el-dialog 函数式调用

vue
<script setup lang="ts">
import { isCancelError, getErrorMessage } from 'vue-select-avatar'

import { ElMessage } from 'element-plus'
import { selectAvatar } from './index'
import { ref } from 'vue'

const src = ref('')
const fileSize = ref(0)
const size = ref(0)

const handleSelect = async () => {
  try {
    const file = await selectAvatar()
    if (src.value) {
      URL.revokeObjectURL(src.value)
    }
    src.value = URL.createObjectURL(file)
    fileSize.value = file.size
  } catch (error) {
    // 错误处理
    if (isCancelError(error)) return
    console.error(error)
    ElMessage.error(getErrorMessage(error))
  }
}

const handleClear = () => {
  src.value = ''
}

const handleLoad = (e: Event) => {
  size.value = (e.target as HTMLImageElement).naturalWidth
}

// 辅助函数
const formatBytes = (bytes: number, decimals = 2) => {
  const k = 1024
  const dm = decimals < 0 ? 0 : decimals
  const units = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
  const i = Math.floor(Math.log(bytes) / Math.log(k))
  return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + units[i] + 'B'
}
</script>

<template>
  <button @click="handleSelect" class="select-avatar-button">选择头像</button>
  <template v-if="src">
    <div style="font-size: 13px">{{ `${size}x${size} ${formatBytes(fileSize)}` }}</div>
    <img :src="src" @load="handleLoad" />
    <button @click="handleClear">清除</button>
  </template>
</template>

<style scoped>
button {
  all: revert;
}
</style>
ts
import { selectImage, SelectAvatarError } from 'vue-select-avatar'

import Content from './content.vue'
import { createVNode, render } from 'vue'

export const selectAvatar = async () => {
  const res = await selectImage({
    maxFileSize: 20 * 1024 * 1024,
    // 其他配置...
  })

  return new Promise<File>((resolve, reject) => {
    let isConfirm = false
    let file: File | undefined
    let error: Error | undefined

    const el = document.createElement('div')

    const vnode = createVNode(Content, {
      info: res,
      onConfirm: (_file: File) => {
        file = _file
        isConfirm = true
      },
      onClose: () => {
        render(null, el)
        el.remove()
        if (isConfirm) {
          resolve(file!)
        } else {
          reject(error || new SelectAvatarError('CANCEL'))
        }
      },
      onError(err: Error) {
        error = err
      },
    })

    render(vnode, el)

    document.body.appendChild(el)
  })
}
vue
<script setup lang="ts">
import 'vue-select-avatar/style.css'
import { Viewport, type ImageInfo } from 'vue-select-avatar'

import { ElDialog, ElButton } from 'element-plus'
import { ref, watchEffect } from 'vue'

interface Props {
  info: ImageInfo
}

defineProps<Props>()

const viewportRef = ref<InstanceType<typeof Viewport>>()
const visible = ref(true)

const emit = defineEmits(['close', 'confirm', 'error'])

const handleCancel = () => {
  visible.value = false
}

const handleConfirm = async () => {
  try {
    const file = await viewportRef.value?.cropper<File>({
      // 裁剪配置...
    })
    emit('confirm', file)
    visible.value = false
  } catch (error) {
    emit('error', error)
    visible.value = false
  }
}

watchEffect(() => {
  if (!visible.value) {
    emit('close')
  }
})
</script>

<template>
  <el-dialog v-model="visible" title="选择图片" width="332" append-to-body>
    <Viewport grid :info="info" ref="viewportRef" />
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="handleCancel">取消</el-button>
        <el-button type="primary" @click="handleConfirm">确定</el-button>
      </div>
    </template>
  </el-dialog>
</template>