feat: 使用tailwindcss 重构样式
parent
66d8690238
commit
61c086e02a
|
@ -28,13 +28,10 @@ const props = withDefaults(defineProps<CropperAvatarProps>(), {
|
|||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
||||
const sourceValue = ref(props.value || '');
|
||||
const prefixCls = 'cropper-avatar';
|
||||
const [CropperModal, modalApi] = useVbenModal({
|
||||
connectedComponent: cropperModal,
|
||||
});
|
||||
|
||||
const getClass = computed(() => [prefixCls]);
|
||||
|
||||
const getWidth = computed(() => `${`${props.width}`.replace(/px/, '')}px`);
|
||||
|
||||
const getIconWidth = computed(
|
||||
|
@ -74,29 +71,42 @@ defineExpose({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="getClass" :style="getStyle">
|
||||
<!-- 头像容器 -->
|
||||
<div class="inline-block text-center" :style="getStyle">
|
||||
<!-- 图片包装器 -->
|
||||
<div
|
||||
:class="`${prefixCls}-image-wrapper`"
|
||||
class="group relative cursor-pointer overflow-hidden rounded-full border border-gray-200 bg-white"
|
||||
:style="getImageWrapperStyle"
|
||||
@click="openModal"
|
||||
>
|
||||
<div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
|
||||
<!-- 遮罩层 -->
|
||||
<div
|
||||
class="duration-400 absolute inset-0 flex cursor-pointer items-center justify-center rounded-full bg-black bg-opacity-40 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
:style="getImageWrapperStyle"
|
||||
>
|
||||
<IconifyIcon
|
||||
icon="lucide:cloud-upload"
|
||||
class="text-gray-400"
|
||||
class="m-auto text-gray-400"
|
||||
:style="{
|
||||
...getImageWrapperStyle,
|
||||
width: `${getIconWidth}`,
|
||||
height: `${getIconWidth}`,
|
||||
lineHeight: `${getIconWidth}`,
|
||||
width: getIconWidth,
|
||||
height: getIconWidth,
|
||||
lineHeight: getIconWidth,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<img v-if="sourceValue" :src="sourceValue" alt="avatar" />
|
||||
<!-- 头像图片 -->
|
||||
<img
|
||||
v-if="sourceValue"
|
||||
:src="sourceValue"
|
||||
alt="avatar"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<!-- 上传按钮 -->
|
||||
<Button
|
||||
v-if="showBtn"
|
||||
:class="`${prefixCls}-upload-btn`"
|
||||
class="mx-auto mt-2"
|
||||
@click="openModal"
|
||||
v-bind="btnProps"
|
||||
>
|
||||
|
@ -111,49 +121,3 @@ defineExpose({
|
|||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cropper-avatar {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
|
||||
&-image-wrapper {
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 50%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-image-mask {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
cursor: pointer;
|
||||
background: rgb(0 0 0 / 40%);
|
||||
border: inherit;
|
||||
border-radius: inherit;
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s;
|
||||
|
||||
::v-deep(svg) {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&-image-mask:hover {
|
||||
opacity: 40;
|
||||
}
|
||||
|
||||
&-upload-btn {
|
||||
margin: 10px auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -37,7 +37,6 @@ const cropper = ref<CropperType>();
|
|||
let scaleX = 1;
|
||||
let scaleY = 1;
|
||||
|
||||
const prefixCls = 'cropper-am';
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
onConfirm: handleOk,
|
||||
onOpenChange(isOpen) {
|
||||
|
@ -129,9 +128,13 @@ async function handleOk() {
|
|||
:title="$t('ui.cropper.modalTitle')"
|
||||
class="w-2/3"
|
||||
>
|
||||
<div :class="prefixCls">
|
||||
<div :class="`${prefixCls}-left`" class="w-full">
|
||||
<div :class="`${prefixCls}-cropper`">
|
||||
<div class="flex h-96">
|
||||
<!-- 左侧区域 -->
|
||||
<div class="h-full w-3/5">
|
||||
<!-- 裁剪器容器 -->
|
||||
<div
|
||||
class="relative h-[300px] bg-gradient-to-b from-neutral-50 to-neutral-200"
|
||||
>
|
||||
<CropperImage
|
||||
v-if="src"
|
||||
:circled="circled"
|
||||
|
@ -142,7 +145,8 @@ async function handleOk() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div :class="`${prefixCls}-toolbar`">
|
||||
<!-- 工具栏 -->
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<Upload
|
||||
:before-upload="handleBeforeUpload"
|
||||
:file-list="[]"
|
||||
|
@ -266,16 +270,26 @@ async function handleOk() {
|
|||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="`${prefixCls}-right`">
|
||||
<div :class="`${prefixCls}-preview`">
|
||||
|
||||
<!-- 右侧区域 -->
|
||||
<div class="h-full w-2/5">
|
||||
<!-- 预览区域 -->
|
||||
<div
|
||||
class="mx-auto h-56 w-56 overflow-hidden rounded-full border border-gray-200"
|
||||
>
|
||||
<img
|
||||
v-if="previewSource"
|
||||
:alt="$t('ui.cropper.preview')"
|
||||
:src="previewSource"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 头像组合预览 -->
|
||||
<template v-if="previewSource">
|
||||
<div :class="`${prefixCls}-group`">
|
||||
<div
|
||||
class="mt-2 flex items-center justify-around border-t border-gray-200 pt-2"
|
||||
>
|
||||
<Avatar :src="previewSource" size="large" />
|
||||
<Avatar :size="48" :src="previewSource" />
|
||||
<Avatar :size="64" :src="previewSource" />
|
||||
|
@ -286,76 +300,3 @@ async function handleOk() {
|
|||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.cropper-am {
|
||||
display: flex;
|
||||
|
||||
&-left,
|
||||
&-right {
|
||||
height: 340px;
|
||||
}
|
||||
|
||||
&-left {
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
&-right {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
&-cropper {
|
||||
height: 300px;
|
||||
background: #eee;
|
||||
background-image:
|
||||
linear-gradient(
|
||||
45deg,
|
||||
rgb(0 0 0 / 25%) 25%,
|
||||
transparent 0,
|
||||
transparent 75%,
|
||||
rgb(0 0 0 / 25%) 0
|
||||
),
|
||||
linear-gradient(
|
||||
45deg,
|
||||
rgb(0 0 0 / 25%) 25%,
|
||||
transparent 0,
|
||||
transparent 75%,
|
||||
rgb(0 0 0 / 25%) 0
|
||||
);
|
||||
background-position:
|
||||
0 0,
|
||||
12px 12px;
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
|
||||
&-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&-preview {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 50%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding-top: 8px;
|
||||
margin-top: 8px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -33,7 +33,6 @@ const imgElRef = ref<ElRef<HTMLImageElement>>();
|
|||
const cropper = ref<Cropper | null>();
|
||||
const isReady = ref(false);
|
||||
|
||||
const prefixCls = 'cropper-image';
|
||||
const debounceRealTimeCropped = useDebounceFn(realTimeCropped, 80);
|
||||
|
||||
const getImageStyle = computed((): CSSProperties => {
|
||||
|
@ -46,10 +45,9 @@ const getImageStyle = computed((): CSSProperties => {
|
|||
|
||||
const getClass = computed(() => {
|
||||
return [
|
||||
prefixCls,
|
||||
attrs.class,
|
||||
{
|
||||
[`${prefixCls}--circled`]: props.circled,
|
||||
'cropper-image--circled': props.circled,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
@ -115,10 +113,9 @@ function cropped() {
|
|||
imgInfo,
|
||||
});
|
||||
};
|
||||
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
||||
fileReader.onerror = () => {
|
||||
fileReader.addEventListener('error', () => {
|
||||
emit('cropendError');
|
||||
};
|
||||
});
|
||||
}, 'image/png');
|
||||
}
|
||||
|
||||
|
@ -157,6 +154,7 @@ function getRoundedCanvas() {
|
|||
:crossorigin="crossorigin"
|
||||
:src="src"
|
||||
:style="getImageStyle"
|
||||
class="h-auto max-w-full"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
Loading…
Reference in New Issue