feat: 使用tailwindcss 重构样式

pull/150/head
xingyu4j 2025-06-18 15:53:53 +08:00
parent 66d8690238
commit 61c086e02a
3 changed files with 48 additions and 145 deletions

View File

@ -28,13 +28,10 @@ const props = withDefaults(defineProps<CropperAvatarProps>(), {
const emit = defineEmits(['update:value', 'change']); const emit = defineEmits(['update:value', 'change']);
const sourceValue = ref(props.value || ''); const sourceValue = ref(props.value || '');
const prefixCls = 'cropper-avatar';
const [CropperModal, modalApi] = useVbenModal({ const [CropperModal, modalApi] = useVbenModal({
connectedComponent: cropperModal, connectedComponent: cropperModal,
}); });
const getClass = computed(() => [prefixCls]);
const getWidth = computed(() => `${`${props.width}`.replace(/px/, '')}px`); const getWidth = computed(() => `${`${props.width}`.replace(/px/, '')}px`);
const getIconWidth = computed( const getIconWidth = computed(
@ -74,29 +71,42 @@ defineExpose({
</script> </script>
<template> <template>
<div :class="getClass" :style="getStyle"> <!-- 头像容器 -->
<div class="inline-block text-center" :style="getStyle">
<!-- 图片包装器 -->
<div <div
:class="`${prefixCls}-image-wrapper`" class="group relative cursor-pointer overflow-hidden rounded-full border border-gray-200 bg-white"
:style="getImageWrapperStyle" :style="getImageWrapperStyle"
@click="openModal" @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 <IconifyIcon
icon="lucide:cloud-upload" icon="lucide:cloud-upload"
class="text-gray-400" class="m-auto text-gray-400"
:style="{ :style="{
...getImageWrapperStyle, ...getImageWrapperStyle,
width: `${getIconWidth}`, width: getIconWidth,
height: `${getIconWidth}`, height: getIconWidth,
lineHeight: `${getIconWidth}`, lineHeight: getIconWidth,
}" }"
/> />
</div> </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> </div>
<!-- 上传按钮 -->
<Button <Button
v-if="showBtn" v-if="showBtn"
:class="`${prefixCls}-upload-btn`" class="mx-auto mt-2"
@click="openModal" @click="openModal"
v-bind="btnProps" v-bind="btnProps"
> >
@ -111,49 +121,3 @@ defineExpose({
/> />
</div> </div>
</template> </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>

View File

@ -37,7 +37,6 @@ const cropper = ref<CropperType>();
let scaleX = 1; let scaleX = 1;
let scaleY = 1; let scaleY = 1;
const prefixCls = 'cropper-am';
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
onConfirm: handleOk, onConfirm: handleOk,
onOpenChange(isOpen) { onOpenChange(isOpen) {
@ -129,9 +128,13 @@ async function handleOk() {
:title="$t('ui.cropper.modalTitle')" :title="$t('ui.cropper.modalTitle')"
class="w-2/3" class="w-2/3"
> >
<div :class="prefixCls"> <div class="flex h-96">
<div :class="`${prefixCls}-left`" class="w-full"> <!-- 左侧区域 -->
<div :class="`${prefixCls}-cropper`"> <div class="h-full w-3/5">
<!-- 裁剪器容器 -->
<div
class="relative h-[300px] bg-gradient-to-b from-neutral-50 to-neutral-200"
>
<CropperImage <CropperImage
v-if="src" v-if="src"
:circled="circled" :circled="circled"
@ -142,7 +145,8 @@ async function handleOk() {
/> />
</div> </div>
<div :class="`${prefixCls}-toolbar`"> <!-- 工具栏 -->
<div class="mt-4 flex items-center justify-between">
<Upload <Upload
:before-upload="handleBeforeUpload" :before-upload="handleBeforeUpload"
:file-list="[]" :file-list="[]"
@ -266,16 +270,26 @@ async function handleOk() {
</Space> </Space>
</div> </div>
</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 <img
v-if="previewSource" v-if="previewSource"
:alt="$t('ui.cropper.preview')" :alt="$t('ui.cropper.preview')"
:src="previewSource" :src="previewSource"
class="h-full w-full object-cover"
/> />
</div> </div>
<!-- 头像组合预览 -->
<template v-if="previewSource"> <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 :src="previewSource" size="large" />
<Avatar :size="48" :src="previewSource" /> <Avatar :size="48" :src="previewSource" />
<Avatar :size="64" :src="previewSource" /> <Avatar :size="64" :src="previewSource" />
@ -286,76 +300,3 @@ async function handleOk() {
</div> </div>
</Modal> </Modal>
</template> </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>

View File

@ -33,7 +33,6 @@ const imgElRef = ref<ElRef<HTMLImageElement>>();
const cropper = ref<Cropper | null>(); const cropper = ref<Cropper | null>();
const isReady = ref(false); const isReady = ref(false);
const prefixCls = 'cropper-image';
const debounceRealTimeCropped = useDebounceFn(realTimeCropped, 80); const debounceRealTimeCropped = useDebounceFn(realTimeCropped, 80);
const getImageStyle = computed((): CSSProperties => { const getImageStyle = computed((): CSSProperties => {
@ -46,10 +45,9 @@ const getImageStyle = computed((): CSSProperties => {
const getClass = computed(() => { const getClass = computed(() => {
return [ return [
prefixCls,
attrs.class, attrs.class,
{ {
[`${prefixCls}--circled`]: props.circled, 'cropper-image--circled': props.circled,
}, },
]; ];
}); });
@ -115,10 +113,9 @@ function cropped() {
imgInfo, imgInfo,
}); });
}; };
// eslint-disable-next-line unicorn/prefer-add-event-listener fileReader.addEventListener('error', () => {
fileReader.onerror = () => {
emit('cropendError'); emit('cropendError');
}; });
}, 'image/png'); }, 'image/png');
} }
@ -157,6 +154,7 @@ function getRoundedCanvas() {
:crossorigin="crossorigin" :crossorigin="crossorigin"
:src="src" :src="src"
:style="getImageStyle" :style="getImageStyle"
class="h-auto max-w-full"
/> />
</div> </div>
</template> </template>