2026-04-13 14:35:12 +08:00
|
|
|
<template>
|
|
|
|
|
<a-modal
|
|
|
|
|
class="image-clip-dialog generalModel"
|
|
|
|
|
v-model:visible="show"
|
|
|
|
|
:footer="null"
|
|
|
|
|
width="70%"
|
|
|
|
|
:maskClosable="false"
|
|
|
|
|
:centered="true"
|
|
|
|
|
:closable="false"
|
|
|
|
|
wrapClassName="#app"
|
|
|
|
|
:keyboard="false"
|
|
|
|
|
>
|
|
|
|
|
<div class="image-clip-dialog-box">
|
|
|
|
|
<div class="header">
|
|
|
|
|
<div class="title">{{ data.title }}</div>
|
|
|
|
|
<div class="right">
|
|
|
|
|
<div class="submit" v-if="!data.isPreview" @click="onSubmit">
|
|
|
|
|
<svg-icon name="seller-dui" size="24" />
|
|
|
|
|
</div>
|
|
|
|
|
<button @click="onCancel">Cancel</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="content">
|
|
|
|
|
<image-clip
|
|
|
|
|
ref="imageClipRef"
|
2026-04-17 17:59:51 +08:00
|
|
|
v-bind="$attrs"
|
2026-04-13 14:35:12 +08:00
|
|
|
:ratio="data.ratio"
|
|
|
|
|
:url="data.url"
|
|
|
|
|
@change="(v) => (data.preview_url = v)"
|
|
|
|
|
/>
|
|
|
|
|
<div class="preview" v-if="data.isPreview">
|
|
|
|
|
<div class="title">
|
|
|
|
|
<span class="icon"><svg-icon name="seller-preview" size="24" /></span>
|
|
|
|
|
<span class="label">Crop Preview</span>
|
|
|
|
|
</div>
|
|
|
|
|
<img :src="data.preview_url" />
|
|
|
|
|
<div class="submit" @click="onSubmit">
|
|
|
|
|
<svg-icon name="seller-dui" size="24" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</a-modal>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref } from "vue"
|
|
|
|
|
import ImageClip from "./image-clip.vue"
|
|
|
|
|
const data = reactive({
|
|
|
|
|
url: "",
|
|
|
|
|
title: "Crop Image",
|
|
|
|
|
preview_url: "",
|
|
|
|
|
ratio: [1, 1],
|
|
|
|
|
isPreview: true,
|
|
|
|
|
callback: null
|
|
|
|
|
})
|
|
|
|
|
const show = ref(false)
|
|
|
|
|
const open = (url, callback, options) => {
|
|
|
|
|
if (!url || !callback) return
|
|
|
|
|
data.url = url
|
|
|
|
|
data.callback = callback
|
|
|
|
|
data.ratio = [1, 1]
|
|
|
|
|
data.isPreview = true
|
|
|
|
|
data.preview_url = ""
|
|
|
|
|
data.title = "Crop Image"
|
|
|
|
|
if (options) {
|
|
|
|
|
if (options.hasOwnProperty("isPreview")) data.isPreview = options.isPreview
|
|
|
|
|
if (options.hasOwnProperty("ratio")) data.ratio = options.ratio
|
|
|
|
|
if (options.hasOwnProperty("title")) data.title = options.title
|
|
|
|
|
}
|
|
|
|
|
show.value = true
|
|
|
|
|
}
|
|
|
|
|
const onCancel = () => {
|
|
|
|
|
show.value = false
|
|
|
|
|
}
|
|
|
|
|
const imageClipRef = ref(null)
|
|
|
|
|
const onSubmit = () => {
|
|
|
|
|
imageClipRef.value.getCropBlob().then((blob) => {
|
|
|
|
|
if (data.callback) data.callback(blobToFile(blob, "image.png"))
|
|
|
|
|
onCancel()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
// 将blob转换为file对象
|
|
|
|
|
const blobToFile = (blob, fileName) => {
|
|
|
|
|
return new File([blob], fileName, { type: blob.type })
|
|
|
|
|
}
|
|
|
|
|
defineExpose({
|
|
|
|
|
open
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
|
.image-clip-dialog-box {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
.submit {
|
|
|
|
|
width: 4rem;
|
|
|
|
|
height: 4rem;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: #262626;
|
|
|
|
|
color: #fff;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
> .header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
margin-bottom: 5rem;
|
|
|
|
|
> .title {
|
|
|
|
|
font-family: pingfang_heavy;
|
|
|
|
|
font-size: 2.4rem;
|
|
|
|
|
color: #595959;
|
|
|
|
|
}
|
|
|
|
|
> .right {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 2rem;
|
|
|
|
|
> button {
|
|
|
|
|
width: 10rem;
|
|
|
|
|
height: 4.8rem;
|
|
|
|
|
border-radius: 4rem;
|
|
|
|
|
border: none;
|
|
|
|
|
background: #e4e5eb;
|
|
|
|
|
font-family: pingfang_heavy;
|
|
|
|
|
font-size: 1.6rem;
|
|
|
|
|
color: #000;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
> .content {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
> .image-clip {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
> .preview {
|
|
|
|
|
margin-left: 6rem;
|
|
|
|
|
width: 28rem;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 2.4rem;
|
|
|
|
|
> .title {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 1.2rem;
|
|
|
|
|
> .label {
|
|
|
|
|
font-family: pingfang_heavy;
|
|
|
|
|
font-size: 1.6rem;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
> img {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: auto;
|
|
|
|
|
margin-bottom: 3rem;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|