| <template> | 
|   <view class="tn-fab-class tn-fab" @touchmove.stop.prevent> | 
|     <view | 
|       class="tn-fab__box" | 
|       :class="{'tn-fab--right': left === 'auto'}" | 
|       :style="{ | 
|         left: $t.string.getLengthUnitValue(fabLeft || left), | 
|         right: $t.string.getLengthUnitValue(fabRight || right), | 
|         bottom: $t.string.getLengthUnitValue(fabBottom || bottom), | 
|         zIndex: elZIndex | 
|       }" | 
|     > | 
|       <view | 
|         v-if="visibleSync" | 
|         class="tn-fab__btns" | 
|         :class="[`tn-fab__btns__animation--${animationType}`,  | 
|           showFab ? `tn-fab__btns--visible--${animationType}` : '' | 
|         ]" | 
|       > | 
|         <view | 
|           v-for="(item, index) in btnList" | 
|           :key="index" | 
|           class="tn-fab__item" | 
|           :class="[ | 
|             `tn-fab__item__animation--${animationType}`,  | 
|             {'tn-fab__item--left': right === 'auto'} | 
|           ]" | 
|           :style="[fabItemStyle(index)]" | 
|           @tap.stop="handleClick(index)" | 
|         > | 
|           <!-- 带图标或者图片时显示的文字信息 --> | 
|           <view | 
|             v-if="animationType !== 'around' && (item.imgUrl || item.icon)" | 
|             :class="[left === 'auto' ? 'tn-fab__item__text--right' : 'tn-fab__item__text--left']" | 
|             :style="{ | 
|               color: item.textColor || '#FFF', | 
|               fontSize: $t.string.getLengthUnitValue(item.textSize || 28) | 
|             }" | 
|           >{{ item.text || '' }}</view> | 
|            | 
|           <!-- 带图片或者图标时的图片、图标信息 --> | 
|           <view | 
|             class="tn-fab__item__btn" | 
|             :class="[!item.bgColor ? backgroundColorClass : '']" | 
|             :style="{ | 
|               width: $t.string.getLengthUnitValue(width), | 
|               height: $t.string.getLengthUnitValue(height), | 
|               lineHeight: $t.string.getLengthUnitValue(height), | 
|               backgroundColor: item.bgColor || backgroundColorStyle || '#01BEFF', | 
|               borderRadius: $t.string.getLengthUnitValue(radius) | 
|             }" | 
|           > | 
|             <!-- 无图片和无图标时只显示文字 --> | 
|             <view | 
|               v-if="!item.imgUrl && !item.icon" | 
|               class="tn-fab__item__btn__title" | 
|               :style="{ | 
|                 color: item.textColor || '#fff', | 
|                 fontSize: $t.string.getLengthUnitValue(item.textSize || 28) | 
|               }" | 
|             >{{ item.text || '' }}</view> | 
|             <!-- 图标 --> | 
|             <view | 
|               v-if="item.icon" | 
|               class="tn-fab__item__btn__icon" | 
|               :class="[`tn-icon-${item.icon}`]" | 
|               :style="{ | 
|                 color: item.iconColor || '#fff', | 
|                 fontSize: $t.string.getLengthUnitValue(item.iconSize || iconSize || 64) | 
|               }" | 
|             ></view> | 
|             <!-- 图片 --> | 
|             <image | 
|               v-else-if="!item.icon && item.imgUrl" | 
|               class="tn-fab__item__btn__image" | 
|               :style="{ | 
|                 width: $t.string.getLengthUnitValue(item.imgWidth || 64), | 
|                 height: $t.string.getLengthUnitValue(item.imgHeight || 64), | 
|               }" | 
|               :src="item.imgUrl" | 
|             ></image> | 
|           </view> | 
|         </view> | 
|       </view> | 
|        | 
|       <view | 
|         class="tn-fab__item__btn tn-fab__item__btn--fab" | 
|         :class="[backgroundColorClass, fontColorClass, {'tn-fab__item__btn--active': showFab}]" | 
|         :style="{ | 
|           width: $t.string.getLengthUnitValue(width), | 
|           height: $t.string.getLengthUnitValue(height), | 
|           backgroundColor: backgroundColorStyle || !backgroundColorClass ? '#01BEFF' : '', | 
|           color: fontColorStyle || '#fff', | 
|           borderRadius: $t.string.getLengthUnitValue(radius), | 
|           zIndex: elZIndex - 1 | 
|         }" | 
|         @tap.stop="fabClick" | 
|       > | 
|         <slot> | 
|           <view class="tn-fab__item__btn__icon" :class="[`tn-icon-${icon}`]" :style="{fontSize: $t.string.getLengthUnitValue(iconSize || 64)}"></view> | 
|         </slot> | 
|       </view> | 
|     </view> | 
|     <view v-if="visibleSync && showMask" class="tn-fab__mask" :class="{'tn-fab__mask--visible': showFab}" :style="{zIndex: elZIndex - 3}" @tap="clickMask()"></view> | 
|   </view> | 
| </template> | 
|   | 
| <script> | 
|   import componentsColorMixin from '../../libs/mixin/components_color.js' | 
|   export default { | 
|     name: 'tn-fab', | 
|     mixins: [componentsColorMixin], | 
|     props: { | 
|       // 按钮列表 | 
|       // { | 
|       //   // 背景颜色 | 
|       //   bgColor: '#fff', | 
|       //   // 图片地址 | 
|       //   imgUrl: '', | 
|       //   // 图片宽度 | 
|       //   imgWidth: 60, | 
|       //   // 图片高度 | 
|       //   imgHeight: 60, | 
|       //   // 图标名称 | 
|       //   icon: '', | 
|       //   // 图标尺寸 | 
|       //   iconSize: 60, | 
|       //   // 图标颜色 | 
|       //   iconColor: '#fff', | 
|       //   // 提示文字 | 
|       //   text: '', | 
|       //   // 文字大小 | 
|       //   textSize: 30, | 
|       //   // 字体颜色 | 
|       //   textColor: '#fff' | 
|       // } | 
|       btnList: { | 
|         type: Array, | 
|         default() { | 
|           return [] | 
|         } | 
|       }, | 
|       // 自定义悬浮按钮内容 | 
|       customBtn: { | 
|         type: Boolean, | 
|         default: false | 
|       }, | 
|       // 悬浮按钮的宽度 | 
|       width: { | 
|         type: [String, Number], | 
|         default: 88 | 
|       }, | 
|       // 悬浮按钮的高度 | 
|       height: { | 
|         type: [String, Number], | 
|         default: 88 | 
|       }, | 
|       // 图标大小 | 
|       iconSize: { | 
|         type: [String, Number], | 
|         default: 64 | 
|       }, | 
|       // 图标名称 | 
|       icon: { | 
|         type: String, | 
|         default: 'open' | 
|       }, | 
|       // 按钮圆角 | 
|       radius: { | 
|         type: [String, Number], | 
|         default: '50%' | 
|       }, | 
|       // 按钮距离左边的位置 | 
|       left: { | 
|         type: [String, Number], | 
|         default: 'auto' | 
|       }, | 
|       // 按钮距离右边的位置 | 
|       right: { | 
|         type: [String, Number], | 
|         default: 'auto' | 
|       }, | 
|       // 按钮距离底部的位置 | 
|       bottom: { | 
|         type: [String, Number], | 
|         default: 100 | 
|       }, | 
|       // 展示动画类型 up 往上展示 around 环绕 | 
|       animationType: { | 
|         type: String, | 
|         default: 'up' | 
|       }, | 
|       // 当动画为圆环时,每个弹出按钮之间的距离, 单位px | 
|       aroundBtnDistance: { | 
|         type: Number, | 
|         default: 10 | 
|       }, | 
|       zIndex: { | 
|         type: Number, | 
|         default: 0 | 
|       }, | 
|       // 显示遮罩 | 
|       showMask: { | 
|         type: Boolean, | 
|         default: true | 
|       }, | 
|       // 点击遮罩是否可以关闭 | 
|       maskCloseable: { | 
|         type: Boolean, | 
|         default: true | 
|       } | 
|     }, | 
|     data() { | 
|       return { | 
|         showFab: false, | 
|         visibleSync: false, | 
|         timer: null, | 
|         fabLeft: 0, | 
|         fabRight: 0, | 
|         fabBottom: 0, | 
|         fabBtnInfo: { | 
|           width: 0, | 
|           height: 0, | 
|           left: 0, | 
|           right: 0, | 
|           bottom: 0 | 
|         }, | 
|         systemInfo: { | 
|           width: 0, | 
|           height: 0 | 
|         }, | 
|         updateProps: false | 
|       } | 
|     }, | 
|     computed: { | 
|       elZIndex() { | 
|         return this.zIndex || this.$t.zIndex.fab | 
|       }, | 
|       propsData() { | 
|         return [this.width, this.height, this.left, this.right, this.bottom] | 
|       }, | 
|       fabItemStyle() { | 
|         return (index) => { | 
|           let style = { | 
|             zIndex: this.elZIndex - 2 | 
|           } | 
|           if (this.animationType === 'up' || !this.showFab) { | 
|             return style | 
|           } | 
|           let base = 1  | 
|           if (this.left === 'auto') { | 
|             base = 1 | 
|           } else if (this.right === 'auto') { | 
|             base = -1 | 
|           } | 
|           style.transform = `rotate(${base * index * 60}deg) translateX(${(this.aroundBtnDistance + this.fabBtnInfo.width) * (-(base))}px)` | 
|           return style | 
|         } | 
|       } | 
|     }, | 
|     watch: { | 
|       propsData() { | 
|         // 更新按钮信息 | 
|         this.updateProps = true | 
|       } | 
|     }, | 
|     mounted() { | 
|       this.$nextTick(() => { | 
|         this.getFabBtnRectInfo() | 
|       }) | 
|     }, | 
|     beforeDestroy() { | 
|       if (this.timer) { | 
|         clearTimeout(this.timer) | 
|       } | 
|     }, | 
|     methods: { | 
|       // 按钮点击事件 | 
|       handleClick(index) { | 
|         this.close() | 
|         this.$emit('click', {index: index}) | 
|       }, | 
|       // 点击悬浮按钮 | 
|       fabClick() { | 
|         if (this.showFab) { | 
|           this.close() | 
|         } else { | 
|           // console.log(this.visibleSync); | 
|           if (this.visibleSync) { | 
|             this.visibleSync = false | 
|             return | 
|           } | 
|           this.open() | 
|         } | 
|       }, | 
|       // 点击遮罩 | 
|       clickMask() { | 
|         if (!this.showMask || !this.maskCloseable) return | 
|         this.close() | 
|       }, | 
|        | 
|       open() { | 
|         this.change('visibleSync', 'showFab', true) | 
|         this.translateFabPosition() | 
|       }, | 
|       close() { | 
|         this.change('showFab', 'visibleSync', false) | 
|         this.fabLeft = 0 | 
|         this.fabRight = 0 | 
|         this.fabBottom = 0 | 
|       }, | 
|       // 关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件 | 
|       // 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用 | 
|       change(param1, param2, status) { | 
|         this[param1] = status | 
|         if (status) { | 
|           // #ifdef H5 || MP | 
|           this.timer = setTimeout(() => { | 
|             this[param2] = status | 
|             this.$emit(status ? 'open' : 'close') | 
|             clearTimeout(this.timer) | 
|           }, 10) | 
|           // #endif | 
|           // #ifndef H5 || MP | 
|           this.$nextTick(() => { | 
|             this[param2] = status | 
|             this.$emit(status ? 'open' : 'close') | 
|           }) | 
|           // #endif | 
|         } else { | 
|           this.timer = setTimeout(() => { | 
|             this[param2] = status | 
|             this.$emit(status ? 'open' : 'close') | 
|             clearTimeout(this.timer) | 
|           }, 250) | 
|         } | 
|       }, | 
|        | 
|       /******************** 旋转动画相关函数 ********************/ | 
|       // 获取按钮的信息 | 
|       async getFabBtnRectInfo() { | 
|         const systemInfo = uni.getSystemInfoSync() | 
|         const btnRectInfo = await this._tGetRect('.tn-fab__item__btn--fab') | 
|         if (!btnRectInfo) { | 
|           setTimeout(() => { | 
|             this.getFabBtnRectInfo() | 
|           }, 10) | 
|           return | 
|         } | 
|         console.log(btnRectInfo); | 
|         this.systemInfo = { | 
|           width: systemInfo.windowWidth, | 
|           height: systemInfo.windowHeight | 
|         } | 
|         this.fabBtnInfo.width = btnRectInfo.width | 
|         this.fabBtnInfo.height = btnRectInfo.height | 
|         this.fabBtnInfo.left = btnRectInfo.left | 
|         this.fabBtnInfo.right = btnRectInfo.right | 
|         this.fabBtnInfo.bottom = btnRectInfo.bottom | 
|       }, | 
|       // 更新悬浮按钮的位置 | 
|       translateFabPosition() { | 
|         if (this.updateProps) { | 
|           this.getFabBtnRectInfo() | 
|           this.updateProps = false | 
|         } | 
|         if (this.animationType === 'up') return  | 
|         // 按钮组的宽度 | 
|         const btnGroupWidth = this.fabBtnInfo.width + this.aroundBtnDistance + 10 | 
|         // 判断当前按钮是在左边还是右边 | 
|         if (this.left === 'auto' && btnGroupWidth > this.systemInfo.width - this.fabBtnInfo.right) { | 
|           // 距离不够需要移动 | 
|           this.fabRight = btnGroupWidth + 'px' | 
|         } else if (this.right === 'auto' && btnGroupWidth > this.fabBtnInfo.left) { | 
|           this.fabLeft = btnGroupWidth + 'px' | 
|         } | 
|          | 
|         if (btnGroupWidth > this.systemInfo.height - this.fabBtnInfo.bottom) { | 
|           this.fabBottom = btnGroupWidth + 'px' | 
|         } | 
|       } | 
|     } | 
|   } | 
| </script> | 
|   | 
| <style lang="scss" scoped> | 
|    | 
|   .tn-fab { | 
|     &__box { | 
|       display: flex; | 
|       justify-content: center; | 
|       align-items: flex-start; | 
|       flex-direction: column; | 
|       position: fixed; | 
|       transition: all 0.25s ease-in-out; | 
|     } | 
|      | 
|     &--right { | 
|       align-items: flex-end; | 
|     } | 
|      | 
|     &__btns { | 
|       transition: all 0.25s cubic-bezier(0,.13,0,1.43); | 
|       transform-origin: 80% bottom; | 
|        | 
|       &__animation--up { | 
|         opacity: 0; | 
|         transform: translateY(100%); | 
|       } | 
|       &__animation--around { | 
|         position: absolute; | 
|         top: 0; | 
|         left: 0; | 
|       } | 
|        | 
|       &--visible--up { | 
|         // visibility: visible; | 
|         opacity: 1; | 
|         transform: translateY(0); | 
|       } | 
|       &--visible--around { | 
|         // visibility: visible; | 
|         // opacity: 1; | 
|       } | 
|     } | 
|      | 
|     &__item { | 
|       display: flex; | 
|       justify-content: flex-end; | 
|       align-items: center; | 
|       padding-bottom: 20rpx; | 
|        | 
|       &__animation--around { | 
|         position: absolute; | 
|         top: 0; | 
|         left: 0; | 
|         transition: transform 0.25s ease-in-out; | 
|         transform-origin: 50% 50%; | 
|         padding-bottom: 0 !important; | 
|       } | 
|        | 
|       &--left { | 
|         flex-flow: row-reverse; | 
|       } | 
|        | 
|       &__text { | 
|         &--left { | 
|           padding-left: 14rpx; | 
|         } | 
|         &--right { | 
|           padding-right: 14rpx; | 
|         } | 
|       } | 
|        | 
|       &__btn { | 
|         display: flex; | 
|         align-items: center; | 
|         justify-content: center; | 
|         box-shadow: 0 0 5rpx 2rpx rgba(0, 0, 0, 0.07); | 
|         transition: all 0.2s linear; | 
|          | 
|         &--active { | 
|           animation-name: fab-button-animation; | 
|           animation-duration: 0.2s; | 
|           animation-timing-function: cubic-bezier(0,.13,0,1.43); | 
|         } | 
|          | 
|         &__title { | 
|           width: 90%; | 
|           text-align: center; | 
|           white-space: nowrap; | 
|           overflow: hidden; | 
|           text-overflow: ellipsis; | 
|         } | 
|          | 
|         &__icon { | 
|           text-align: center; | 
|           font-size: 64rpx; | 
|         } | 
|          | 
|         &__image { | 
|           display: block; | 
|         } | 
|       } | 
|     } | 
|      | 
|     &__mask { | 
|       position: fixed; | 
|       top: 0; | 
|       left: 0; | 
|       right: 0; | 
|       bottom: 0; | 
|       background-color: $tn-mask-bg-color; | 
|       transition: all 0.2s ease-in-out; | 
|       opacity: 0; | 
|        | 
|       &--visible { | 
|         opacity: 1; | 
|       } | 
|     } | 
|   } | 
|    | 
|   @keyframes fab-button-animation { | 
|     0% { | 
|       transform: scale(0.6); | 
|     } | 
|     // 20% { | 
|     //   transform: scale(1.8); | 
|     // } | 
|     // 40% { | 
|     //   transform: scale(0.4); | 
|     // } | 
|     // 50% { | 
|     //   transform: scale(1.4); | 
|     // } | 
|     // 80% { | 
|     //   transform: scale(0.8); | 
|     // } | 
|     100% { | 
|       transform: scale(1); | 
|     } | 
|   } | 
| </style> |