Skip to content

Commit 8e68ef3

Browse files
feat: ✨ Fab 悬浮按钮组件支持自定义触发器和控制能否展开 (#612)
Closes: #512
1 parent 10ebf5c commit 8e68ef3

File tree

4 files changed

+114
-40
lines changed

4 files changed

+114
-40
lines changed

docs/component/fab.md

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -42,59 +42,90 @@ const disabled = ref<boolean>(false)
4242
```
4343

4444
```scss
45-
:deep(.custom-button) {
46-
min-width: auto !important;
47-
box-sizing: border-box;
48-
width: 32px !important;
49-
height: 32px !important;
50-
border-radius: 16px !important;
51-
margin: 8rpx;
52-
}
53-
54-
:deep(.custom-radio) {
55-
height: 32px !important;
56-
line-height: 32px !important;
57-
}
45+
:deep(.custom-button) {
46+
min-width: auto !important;
47+
box-sizing: border-box;
48+
width: 32px !important;
49+
height: 32px !important;
50+
border-radius: 16px !important;
51+
margin: 8rpx;
52+
}
53+
54+
:deep(.custom-radio) {
55+
height: 32px !important;
56+
line-height: 32px !important;
57+
}
5858
```
5959

6060
## 动作菜单展开/收起
6161

6262
通过`v-model:active`控制动作按钮菜单的展开/收起
6363

6464
```html
65-
<wd-fab v-model:active="active">
65+
<wd-fab v-model:active="active"></wd-fab>
6666
```
6767

6868
```ts
6969
const active = ref<boolean>(false)
70-
7170
```
7271

7372
## 可拖动按钮
7473

7574
```html
76-
<wd-fab :draggable="true">
75+
<wd-fab :draggable="true"></wd-fab>
7776
```
7877

7978
:::warning
8079
开启拖动后`direction`属性将失效,会根据拖动后的位置自动计算弹出方向。拖动完成后按钮将会自动吸边。
8180
:::
8281

82+
## 自定义触发器
83+
84+
通过`trigger`插槽自定义触发器,`expandable`控制点击触发器是否展开/收起动作按钮菜单。
85+
86+
87+
```html
88+
<wd-fab position="left-bottom" :expandable="false">
89+
<template #trigger>
90+
<wd-button @click="handleClick" icon="share" type="error">分享给朋友</wd-button>
91+
</template>
92+
</wd-fab>
93+
```
94+
```ts
95+
const handleClick = () => {
96+
console.log('点击了')
97+
}
98+
99+
```
100+
83101
## Attributes
84102

85-
| 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
86-
| -------------- | ---------------------------------- | ------------ | ----------------------------------------------------------------------------------------- | ---------------------------------------------- | ---------------- |
87-
| v-model:active | 是否激活 | boolean | - | false | 0.1.57 |
88-
| type | 类型 | FabType | 'primary' &#124; 'success' &#124; 'info' &#124; 'warning' &#124; 'error' &#124; 'default' | 'primary' | 0.1.57 |
89-
| position | 悬浮按钮位置 | FabPosition | 'left-top' &#124; 'right-top' &#124; 'left-bottom' &#124; 'right-bottom' | 'right-bottom' | 0.1.57 |
90-
| draggable | 按钮能否拖动 | boolean | | false | 1.2.19 |
91-
| direction | 悬浮按钮菜单弹出方向 | FabDirection | 'top' &#124; 'right' &#124; 'bottom' &#124; 'left' | 'top' | 0.1.57 |
92-
| disabled | 是否禁用 | boolean | - | false | 0.1.57 |
93-
| inactiveIcon | 悬浮按钮未展开时的图标 | string | - | 'add' | 0.1.57 |
94-
| activeIcon | 悬浮按钮展开时的图标 | string | - | 'close' | 0.1.57 |
95-
| zIndex | 自定义悬浮按钮层级 | number | - | 99 | 0.1.57 |
96-
| gap | 自定义悬浮按钮与可视区域边缘的间距 | FabGap | - | \{ top: 16, left: 16, right: 16, bottom: 16 \} | 1.2.26 |
97-
| customStyle | 自定义样式 | string | - | '' | 0.1.57 |
103+
| 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
104+
| -------------- | ----------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------- | ---------------------------------------------- | ---------------- |
105+
| v-model:active | 是否激活 | boolean | - | false | 0.1.57 |
106+
| type | 类型 | FabType | 'primary' &#124; 'success' &#124; 'info' &#124; 'warning' &#124; 'error' &#124; 'default' | 'primary' | 0.1.57 |
107+
| position | 悬浮按钮位置 | FabPosition | 'left-top' &#124; 'right-top' &#124; 'left-bottom' &#124; 'right-bottom' | 'right-bottom' | 0.1.57 |
108+
| draggable | 按钮能否拖动 | boolean | | false | 1.2.19 |
109+
| direction | 悬浮按钮菜单弹出方向 | FabDirection | 'top' &#124; 'right' &#124; 'bottom' &#124; 'left' | 'top' | 0.1.57 |
110+
| disabled | 是否禁用 | boolean | - | false | 0.1.57 |
111+
| inactiveIcon | 悬浮按钮未展开时的图标 | string | - | 'add' | 0.1.57 |
112+
| activeIcon | 悬浮按钮展开时的图标 | string | - | 'close' | 0.1.57 |
113+
| zIndex | 自定义悬浮按钮层级 | number | - | 99 | 0.1.57 |
114+
| gap | 自定义悬浮按钮与可视区域边缘的间距 | FabGap | - | \{ top: 16, left: 16, right: 16, bottom: 16 \} | 1.2.26 |
115+
| customStyle | 自定义样式 | string | - | '' | 0.1.57 |
116+
| expandable | 用于控制点击时是否展开菜单,设置为 false 时触发 click | boolean | - | true | $LOWEST_VERSION$ |
117+
118+
## Events
119+
120+
| 事件名称 | 说明 | 参数 | 最低版本 |
121+
| -------- | -------------------------------------------- | ---- | ---------------- |
122+
| click | expandable 设置为 false 时,点击悬浮按钮触发 || $LOWEST_VERSION$ |
123+
124+
## Slot
125+
126+
| name | 说明 | 最低版本 |
127+
| ------- | -------------------------------------------------------------- | ---------------- |
128+
| trigger | 触发器插槽,用于自定义点击按钮,使用此插槽时组件不会抛出 click | $LOWEST_VERSION$ |
98129

99130
## 外部样式类
100131

src/pages/fab/Index.vue

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,22 @@
4343
<wd-button type="primary" @click="active = !active" round>切换</wd-button>
4444
</view>
4545
</demo-block>
46-
<wd-fab v-model:active="active" :disabled="disabled" :type="type" :position="position" :direction="direction" :draggable="draggable">
46+
47+
<demo-block title="自定义触发器">
48+
<view @click.stop="">
49+
<wd-switch v-model="useTriggerSlot" size="22px" />
50+
</view>
51+
</demo-block>
52+
<wd-fab
53+
v-if="!useTriggerSlot"
54+
v-model:active="active"
55+
:disabled="disabled"
56+
:type="type"
57+
:position="position"
58+
:direction="direction"
59+
:draggable="draggable"
60+
@click="showToast('我被点了')"
61+
>
4762
<wd-button @click="showToast('一键三连')" :disabled="disabled" custom-class="custom-button" type="primary" round>
4863
<wd-icon name="github-filled" size="22px"></wd-icon>
4964
</wd-button>
@@ -58,6 +73,12 @@
5873
<wd-icon name="thumb-up" size="22px"></wd-icon>
5974
</wd-button>
6075
</wd-fab>
76+
77+
<wd-fab v-else position="left-bottom" :draggable="draggable" :expandable="false">
78+
<template #trigger>
79+
<wd-button @click="handleCustomClick" icon="share" type="error">分享给朋友</wd-button>
80+
</template>
81+
</wd-fab>
6182
</page-wraper>
6283
</view>
6384
</template>
@@ -70,14 +91,23 @@ const type = ref<'primary' | 'success' | 'info' | 'warning' | 'error' | 'default
7091
const position = ref<'left-top' | 'right-top' | 'left-bottom' | 'right-bottom'>('left-bottom')
7192
const direction = ref<'top' | 'right' | 'bottom' | 'left'>('top')
7293
const disabled = ref<boolean>(false)
73-
const draggable = ref(false)
94+
const draggable = ref<boolean>(false)
95+
const useTriggerSlot = ref<boolean>(false)
96+
7497
const { closeOutside } = useQueue()
98+
99+
function handleCustomClick() {
100+
showToast('分享给朋友')
101+
}
75102
</script>
76103
<style lang="scss" scoped>
77104
.fab {
78105
position: relative;
79106
height: 100%;
80107
width: 100%;
108+
min-height: 100vh;
109+
box-sizing: border-box;
110+
padding-bottom: 88rpx;
81111
:deep(.custom-button) {
82112
min-width: auto !important;
83113
box-sizing: border-box;

src/uni_modules/wot-design-uni/components/wd-fab/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ export const fabProps = {
4747
gap: {
4848
type: Object as PropType<FabGap>,
4949
default: () => ({})
50-
}
50+
},
51+
/**
52+
* 用于控制点击时是否展开菜单
53+
*/
54+
expandable: makeBooleanProp(true)
5155
}
5256

5357
export type FabProps = ExtractPropTypes<typeof fabProps>

src/uni_modules/wot-design-uni/components/wd-fab/wd-fab.vue

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
@click.stop=""
99
>
1010
<view @click.stop="" :style="{ visibility: inited ? 'visible' : 'hidden' }" id="trigger">
11-
<wd-button @click="handleClick" custom-class="wd-fab__trigger" round :type="type" :disabled="disabled">
11+
<slot name="trigger" v-if="$slots.trigger"></slot>
12+
<wd-button v-else @click="handleClick" custom-class="wd-fab__trigger" round :type="type" :disabled="disabled">
1213
<wd-icon custom-class="wd-fab__icon" :name="isActive ? activeIcon : inactiveIcon"></wd-icon>
1314
</wd-button>
1415
</view>
1516
<wd-transition
17+
v-if="expandable"
1618
:enter-class="`wd-fab__transition-enter--${fabDirection}`"
1719
enter-active-class="wd-fab__transition-enter-active"
1820
:leave-to-class="`wd-fab__transition-leave-to--${fabDirection}`"
@@ -48,9 +50,10 @@ import { type Queue, queueKey } from '../composables/useQueue'
4850
import { closeOther, pushToQueue, removeFromQueue } from '../common/clickoutside'
4951
import { fabProps, type FabExpose } from './types'
5052
import { reactive } from 'vue'
53+
import { useRaf } from '../composables/useRaf'
5154
5255
const props = defineProps(fabProps)
53-
const emit = defineEmits(['update:active'])
56+
const emit = defineEmits(['update:active', 'click'])
5457
const inited = ref<boolean>(false) // 是否初始化完成
5558
const isActive = ref<boolean>(false) // 是否激活状态
5659
const queue = inject<Queue | null>(queueKey, null)
@@ -92,7 +95,7 @@ watch(
9295
const top = ref<number>(0)
9396
const left = ref<number>(0)
9497
const screen = reactive({ width: 0, height: 0 })
95-
const fabSize = ref<number>(56)
98+
const fabSize = reactive({ width: 56, height: 56 })
9699
const bounding = reactive({
97100
minTop: 0,
98101
minLeft: 0,
@@ -104,7 +107,8 @@ async function getBounding() {
104107
const sysInfo = uni.getSystemInfoSync()
105108
try {
106109
const trigerInfo = await getRect('#trigger', false, proxy)
107-
fabSize.value = trigerInfo.width || 56
110+
fabSize.width = trigerInfo.width || 56
111+
fabSize.height = trigerInfo.height || 56
108112
} catch (error) {
109113
console.log(error)
110114
}
@@ -114,8 +118,8 @@ async function getBounding() {
114118
screen.height = isH5 ? sysInfo.windowTop + sysInfo.windowHeight : sysInfo.windowHeight
115119
bounding.minTop = isH5 ? sysInfo.windowTop + top : top
116120
bounding.minLeft = left
117-
bounding.maxLeft = screen.width - fabSize.value - right
118-
bounding.maxTop = screen.height - fabSize.value - bottom
121+
bounding.maxLeft = screen.width - fabSize.width - right
122+
bounding.maxTop = screen.height - fabSize.height - bottom
119123
}
120124
121125
function initPosition() {
@@ -170,7 +174,7 @@ function handleTouchEnd() {
170174
if (props.draggable === false) return
171175
172176
const screenCenterX = screen.width / 2
173-
const fabCenterX = left.value + fabSize.value / 2
177+
const fabCenterX = left.value + fabSize.width / 2
174178
attractTransition.value = true
175179
if (fabCenterX < screenCenterX) {
176180
left.value = bounding.minLeft
@@ -200,11 +204,12 @@ onMounted(() => {
200204
pushToQueue(proxy)
201205
}
202206
203-
nextTick(async () => {
207+
const { start } = useRaf(async () => {
204208
await getBounding()
205209
initPosition()
206210
inited.value = true
207211
})
212+
start()
208213
})
209214
210215
onBeforeUnmount(() => {
@@ -219,6 +224,10 @@ function handleClick() {
219224
if (props.disabled) {
220225
return
221226
}
227+
if (!props.expandable) {
228+
emit('click')
229+
return
230+
}
222231
isActive.value = !isActive.value
223232
emit('update:active', isActive.value)
224233
}

0 commit comments

Comments
 (0)