✨ refine MyUI
This commit is contained in:
398
src/components/MyUI/FloatButton/FloatButton.vue
Normal file
398
src/components/MyUI/FloatButton/FloatButton.vue
Normal file
@@ -0,0 +1,398 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, ref, watch} from 'vue';
|
||||
import Tooltip from '../Tooltip/Tooltip.vue';
|
||||
import Badge from '../Badge/Badge.vue';
|
||||
import {useSlotsExist} from '../Utils';
|
||||
|
||||
interface Props {
|
||||
top?: number | string // 按钮定位的上边距,单位 px
|
||||
bottom?: number | string // 按钮定位的下边距,单位 px
|
||||
left?: number | string // 按钮定位的左边距,单位 px
|
||||
right?: number | string // 按钮定位的右边距,单位 px
|
||||
width?: number | string // 浮动按钮宽度,单位 px
|
||||
height?: number | string // 浮动按钮高度,单位 px
|
||||
type?: 'default' | 'primary' // 浮动按钮类型
|
||||
shape?: 'circle' | 'square' // 浮动按钮形状
|
||||
icon?: string // 浮动按钮图标 string | slot
|
||||
description?: string // 文字描述信息 string | slot
|
||||
href?: string // 点击跳转的地址,指定此属性按钮的行为和 a 链接一致
|
||||
target?: '_self' | '_blank' // 相当于 a 标签的 target 属性,href 存在时生效
|
||||
menuTrigger?: 'click' | 'hover' // 浮动按钮菜单显示的触发方式
|
||||
tooltip?: string // 气泡卡片的内容 string | slot
|
||||
tooltipProps?: object // Tooltip 组件属性配置,参考 Tooltip Props
|
||||
badgeProps?: object // 带徽标的浮动按钮(不支持 status 以及相关属性),参考 Badge Props
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
top: undefined,
|
||||
bottom: 40,
|
||||
left: undefined,
|
||||
right: 40,
|
||||
width: 44,
|
||||
height: 44,
|
||||
type: 'default',
|
||||
shape: 'circle',
|
||||
icon: undefined,
|
||||
description: undefined,
|
||||
href: undefined,
|
||||
target: '_self',
|
||||
menuTrigger: undefined,
|
||||
tooltip: undefined,
|
||||
tooltipProps: () => ({}),
|
||||
badgeProps: () => ({})
|
||||
});
|
||||
const showMenu = ref(false);
|
||||
const emits = defineEmits(['click', 'openChange']);
|
||||
const slotsExist = useSlotsExist(['icon', 'description', 'tooltip', 'menu']);
|
||||
const floatBtnWidth = computed(() => {
|
||||
if (typeof props.width === 'number') {
|
||||
return `${props.width}px`;
|
||||
}
|
||||
return props.width;
|
||||
});
|
||||
const floatBtnHeight = computed(() => {
|
||||
if (typeof props.height === 'number') {
|
||||
return `${props.height}px`;
|
||||
}
|
||||
return props.height;
|
||||
});
|
||||
const floatBtnLeft = computed(() => {
|
||||
if (typeof props.left === 'number') {
|
||||
return `${props.left}px`;
|
||||
}
|
||||
return props.left;
|
||||
});
|
||||
const floatBtnRight = computed(() => {
|
||||
if (props.left) {
|
||||
return null;
|
||||
} else {
|
||||
if (typeof props.right === 'number') {
|
||||
return `${props.right}px`;
|
||||
}
|
||||
return props.right;
|
||||
}
|
||||
});
|
||||
const floatBtnTop = computed(() => {
|
||||
if (typeof props.top === 'number') {
|
||||
return `${props.top}px`;
|
||||
}
|
||||
return props.top;
|
||||
});
|
||||
const floatBtnBottom = computed(() => {
|
||||
if (props.top) {
|
||||
return null;
|
||||
} else {
|
||||
if (typeof props.bottom === 'number') {
|
||||
return `${props.bottom}px`;
|
||||
}
|
||||
return props.bottom;
|
||||
}
|
||||
});
|
||||
const showDescription = computed(() => {
|
||||
return slotsExist.description || props.description;
|
||||
});
|
||||
const showTooltip = computed(() => {
|
||||
return slotsExist.tooltip || props.tooltip;
|
||||
});
|
||||
watch(showMenu, (to) => {
|
||||
emits('openChange', to);
|
||||
});
|
||||
|
||||
function onClick(e: Event) {
|
||||
emits('click', e);
|
||||
if (props.menuTrigger === 'click' && slotsExist.menu) {
|
||||
showMenu.value = !showMenu.value;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<component
|
||||
:is="href ? 'a' : 'div'"
|
||||
tabindex="0"
|
||||
class="m-float-btn"
|
||||
:class="`float-btn-${type} float-btn-${shape}`"
|
||||
:style="`
|
||||
--float-btn-width: ${floatBtnWidth};
|
||||
--float-btn-height: ${floatBtnHeight};
|
||||
--float-btn-left: ${floatBtnLeft};
|
||||
--float-btn-right: ${floatBtnRight};
|
||||
--float-btn-top: ${floatBtnTop};
|
||||
--float-btn-bottom: ${floatBtnBottom}
|
||||
`"
|
||||
:href="href"
|
||||
:target="target"
|
||||
@click="onClick"
|
||||
@blur="menuTrigger === 'click' ? (showMenu = false) : null"
|
||||
@mouseenter="menuTrigger === 'hover' ? (showMenu = true) : null"
|
||||
@mouseleave="menuTrigger === 'hover' ? (showMenu = false) : null"
|
||||
>
|
||||
<Tooltip placement="left" v-bind="tooltipProps" class="float-btn-tooltip">
|
||||
<template v-if="showTooltip" #tooltip>
|
||||
<slot name="tooltip">{{ tooltip }}</slot>
|
||||
</template>
|
||||
<Badge v-bind="badgeProps">
|
||||
<div class="float-btn-body">
|
||||
<div class="float-btn-content">
|
||||
<div v-if="slotsExist.icon" class="float-btn-icon">
|
||||
<Transition name="fade">
|
||||
<slot v-if="!showMenu" name="icon"></slot>
|
||||
<svg
|
||||
v-else
|
||||
class="close-svg"
|
||||
focusable="false"
|
||||
data-icon="close"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
fill-rule="evenodd"
|
||||
viewBox="64 64 896 896"
|
||||
>
|
||||
<path
|
||||
d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
|
||||
></path>
|
||||
</svg>
|
||||
</Transition>
|
||||
</div>
|
||||
<div v-if="showDescription" class="float-btn-description">
|
||||
<slot name="description">{{ description }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
<Transition v-show="showMenu" name="move">
|
||||
<div class="float-btn-menu">
|
||||
<slot name="menu"></slot>
|
||||
</div>
|
||||
</Transition>
|
||||
</component>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.fade-move,
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
transform: scale(0.75);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.move-enter-active,
|
||||
.move-leave-active {
|
||||
transform-origin: 0 0;
|
||||
transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
|
||||
}
|
||||
|
||||
.move-leave-active {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.move-enter-from,
|
||||
.move-leave-to {
|
||||
transform: translate3d(0, var(--float-btn-height), 0);
|
||||
transform-origin: 0 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.m-float-btn {
|
||||
position: fixed;
|
||||
left: var(--float-btn-left);
|
||||
right: var(--float-btn-right);
|
||||
top: var(--float-btn-top);
|
||||
bottom: var(--float-btn-bottom);
|
||||
z-index: 99;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
line-height: 1.5714285714285714;
|
||||
display: inline-block;
|
||||
width: var(--float-btn-width);
|
||||
height: var(--float-btn-height);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.float-btn-tooltip {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
:deep(.tooltip-content) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.m-badge {
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.m-badge-value:not(.only-dot) {
|
||||
transform: translate(0, 0);
|
||||
transform-origin: center;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.float-btn-body {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.2s;
|
||||
|
||||
.float-btn-content {
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
min-height: var(--float-btn-height);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 2px 4px;
|
||||
|
||||
.float-btn-icon {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
|
||||
.close-svg {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
:deep(svg) {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
:deep(img) {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.float-btn-menu {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
display: block;
|
||||
z-index: -1;
|
||||
|
||||
.m-float-btn {
|
||||
position: static;
|
||||
}
|
||||
|
||||
& > * {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.float-btn-default {
|
||||
background-color: #ffffff;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
& > .float-btn-tooltip {
|
||||
.float-btn-body {
|
||||
background-color: #ffffff;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.float-btn-content {
|
||||
.float-btn-icon {
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
}
|
||||
|
||||
.float-btn-description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 16px;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.float-btn-primary {
|
||||
background-color: @themeColor;
|
||||
|
||||
& > .float-btn-tooltip {
|
||||
.float-btn-body {
|
||||
background-color: @themeColor;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #4096ff;
|
||||
}
|
||||
|
||||
.float-btn-content {
|
||||
.float-btn-icon {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.float-btn-description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 16px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.float-btn-circle {
|
||||
border-radius: 50%;
|
||||
|
||||
:deep(.m-badge) {
|
||||
.only-dot {
|
||||
top: 5.857864376269049px;
|
||||
right: 5.857864376269049px;
|
||||
}
|
||||
}
|
||||
|
||||
& > .float-btn-tooltip {
|
||||
.float-btn-body {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.float-btn-square {
|
||||
height: auto;
|
||||
min-height: var(--float-btn-height);
|
||||
border-radius: 8px;
|
||||
|
||||
:deep(.m-badge) {
|
||||
.only-dot {
|
||||
top: 2.3431457505076194px;
|
||||
right: 2.3431457505076194px;
|
||||
}
|
||||
}
|
||||
|
||||
& > .float-btn-tooltip {
|
||||
.float-btn-body {
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user