<template>
  <div class="upload-avatar">
    <div class="el-upload el-upload--picture-card" @click="showDialog = !disabled">
      <el-avatar v-if="avatarUrl" :src="avatarUrl" :size="146" alt="头像" />
      <el-icon v-else class="avatar-uploader-icon">
        <Plus />
      </el-icon>
    </div>
    <slot name="text">
      <div v-show="!disabled" class="el-upload__tip w-400px">
        <slot name="tip">
          单张图片不大于{{ fileSize }}MB，且格式为jpeg、jpg、png或bmp，只可上传1张
        </slot>
      </div>
    </slot>
  </div>
  <el-dialog v-if="!disabled" v-model="showDialog" :title="title" width="800px" append-to-body @opened="modalOpened" @closed="closeDialog">
    <el-row>
      <el-col :xs="24" :md="12" :style="{ height: '350px' }">
        <VueCropper
          v-if="visible" ref="cropperRef" :img="cropperURL" :info="true" :auto-crop="options.autoCrop"
          :auto-crop-width="autoCropWidth" :auto-crop-height="autoCropHeight" :fixed-box="options.fixedBox"
          :output-type="options.outputType" @real-time="realTime"
        />
      </el-col>
      <el-col :xs="24" :md="12" :style="{ height: '350px' }">
        <div
          absolute h-200px w-200px rd="1/2" top="1/2" overflow-hidden
          class="translate-x-1/2 -translate-y-1/2" :style="imageStyle"
        >
          <img v-show="options.previews.url" h-200px w-200px :src="options.previews.url" :style="options.previews.img" alt="实时预览图片">
        </div>
      </el-col>
    </el-row>
    <br>
    <el-row>
      <el-col :lg="2" :md="2">
        <el-button @click="selectImage">
          选择
          <el-icon class="el-icon--right">
            <Upload />
          </el-icon>
        </el-button>
      </el-col>
      <el-col :lg="{ span: 1, offset: 2 }" :md="2">
        <el-button :icon="Plus" @click="changeScale(1)" />
      </el-col>
      <el-col :lg="{ span: 1, offset: 1 }" :md="2">
        <el-button :icon="Minus" @click="changeScale(-1)" />
      </el-col>
      <el-col :lg="{ span: 1, offset: 1 }" :md="2">
        <el-button :icon="RefreshLeft" @click="rotateLeft()" />
      </el-col>
      <el-col :lg="{ span: 1, offset: 1 }" :md="2">
        <el-button :icon="RefreshRight" @click="rotateRight()" />
      </el-col>
      <el-col :lg="{ span: 2, offset: 6 }" :md="2">
        <el-button type="primary" :loading="loading" @click="confirmUpload">
          提 交
        </el-button>
      </el-col>
    </el-row>
  </el-dialog>
</template>

<script lang="ts" setup name="ThUploadAvatar">
import { ElMessage } from 'element-plus'
import type { CSSProperties } from 'vue'
import { VueCropper } from 'vue-cropper'
import { Minus, Plus, RefreshLeft, RefreshRight, Upload } from '@element-plus/icons-vue'

const props = withDefaults(defineProps<{
  accpet?: string
  fileId?: number | string
  fileUrl?: string
  /**
   * 图片大小（单位MB）
   *
   * @default 2
   */
  fileSize?: number
  /** 默认生成截图框宽度 */
  autoCropHeight?: number
  /** 默认生成截图框高度 */
  autoCropWidth?: number
  /** 上传请求函数 */
  uploadFunction?: (formData: FormData) => Promise<FileVO>
  /** 文件信息请求函数 */
  fileFunction: (fileId?: number | string) => Promise<FileVO>
  disabled?: boolean
  title?: string
}>(), {
  accpet: '.jpeg,.jpg,.png,.bmp',
  fileId: '',
  fileUrl: '',
  fileSize: 2,
  autoCropHeight: 200,
  autoCropWidth: 200,
  title: '上传头像',
})

const emit = defineEmits<{
  (event: 'update:fileUrl', fileId?: number): void
  (event: 'update:filelId', modelValue?: string): void
  (event: 'onSuccess', file: FileVO): void
  (event: 'onError', error: Error): void
}>()

let avatarUrl = $ref(props.fileUrl)
const filelId = useVModel(props, 'fileId', emit)

const imageStyle = {
  boxShadow: '0 0 4px #ccc',
}

let loading = $ref(false)

interface PreviewData {
  div: CSSProperties
  h: number
  html: string
  img: CSSProperties
  url: string
  w: number
}

interface Option {
  /** 裁剪图片的地址 */
  img: any
  /** 是否默认生成截图框 */
  autoCrop: boolean
  /** 固定截图框大小 不允许改变 */
  fixedBox: boolean
  /** 默认生成截图为PNG格式 */
  outputType: string
  /** 预览数据 */
  previews: Partial<PreviewData>
  visible: boolean
}

// 图片裁剪数据
const options = reactive<Option>({
  img: '',
  autoCrop: true,
  fixedBox: true,
  outputType: 'png',
  previews: {},
  visible: false,
})

const visible = ref(false)

const cropperRef = $ref<InstanceType<typeof VueCropper>>()
const { open, reset, onChange } = useFileDialog()

let showDialog = $ref(false)
/** 向左旋转 */
function rotateLeft() {
  cropperRef?.rotateLeft()
}
/** 向右旋转 */
function rotateRight() {
  cropperRef?.rotateRight()
}
/** 打开弹出层结束时的回调 */
function modalOpened() {
  visible.value = true
}
/** 图片缩放 */
function changeScale(num?: number) {
  cropperRef?.changeScale(num || 1)
}
/** 实时预览 */
function realTime(data: PreviewData) {
  options.previews = data
}
const file = shallowRef()
const cropperURL = useObjectUrl(file)

onChange((param) => {
  file.value = param?.item(0)
})

function selectImage() {
  open({
    accept: props.accpet,
    multiple: false,
  })
}

async function uploadImg(blob: Blob) {
  if (!props.uploadFunction) {
    ElMessage.error('请定义接口请求函数')
    emit('onError', new Error('请定义接口请求函数'))
    return
  }
  if (blob.size > props.fileSize * 1024 * 1024) {
    ElMessage.error('您的图片经过剪裁依然很大，请重新选择图片')
    emit('onError', new Error('您的图片经过剪裁依然很大，请重新选择图片'))
    return
  }

  loading = true
  const formData = new FormData()
  formData.append('file', blob)

  try {
    const res = await props.uploadFunction(formData)
    avatarUrl = res.url
    filelId.value = res.id
    showDialog = false
    emit('onSuccess', res)
  }
  catch (err: any) {
    emit('onError', err)
    loading = false
  }
}

function confirmUpload() {
  cropperRef?.getCropBlob(uploadImg)
}
/** 关闭窗口 */
function closeDialog() {
  options.visible = false
  reset()
  loading = false
}

watch(() => props.fileId, async () => {
  if (!props.fileId || avatarUrl)
    return

  const res = await props.fileFunction(props.fileId)
  avatarUrl = res.url
})
</script>

<style>
@import url('vue-cropper/dist/index.css');
@import url('element-plus/theme-chalk/el-upload.css');
</style>
