uni-fab.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. <template>
  2. <view>
  3. <view v-if="popMenu && (leftBottom||rightBottom||leftTop||rightTop)" :class="{
  4. 'uni-fab--leftBottom': leftBottom,
  5. 'uni-fab--rightBottom': rightBottom,
  6. 'uni-fab--leftTop': leftTop,
  7. 'uni-fab--rightTop': rightTop
  8. }" class="uni-fab">
  9. <view :class="{
  10. 'uni-fab__content--left': horizontal === 'left',
  11. 'uni-fab__content--right': horizontal === 'right',
  12. 'uni-fab__content--flexDirection': direction === 'vertical',
  13. 'uni-fab__content--flexDirectionStart': flexDirectionStart,
  14. 'uni-fab__content--flexDirectionEnd': flexDirectionEnd,
  15. 'uni-fab__content--other-platform': !isAndroidNvue
  16. }" :style="{ width: boxWidth, height: boxHeight, backgroundColor: styles.backgroundColor }" class="uni-fab__content" elevation="5">
  17. <view v-if="flexDirectionStart || horizontalLeft" class="uni-fab__item uni-fab__item--first" />
  18. <view v-for="(item, index) in content" :key="index" :class="{ 'uni-fab__item--active': isShow }" class="uni-fab__item" @click="_onItemClick(index, item)">
  19. <image :src="item.active ? item.selectedIconPath : item.iconPath" class="uni-fab__item-image" mode="widthFix" />
  20. <text class="uni-fab__item-text" :style="{ color: item.active ? styles.selectedColor : styles.color }">{{ item.text }}</text>
  21. </view>
  22. <view v-if="flexDirectionEnd || horizontalRight" class="uni-fab__item uni-fab__item--first" />
  23. </view>
  24. </view>
  25. <view :class="{
  26. 'uni-fab__circle--leftBottom': leftBottom,
  27. 'uni-fab__circle--rightBottom': rightBottom,
  28. 'uni-fab__circle--leftTop': leftTop,
  29. 'uni-fab__circle--rightTop': rightTop,
  30. 'uni-fab__content--other-platform': !isAndroidNvue
  31. }" class="uni-fab__circle uni-fab__plus" :style="{ 'background-color': styles.buttonColor }" @click="_onClick">
  32. <view class="fab-circle-v" :class="{'uni-fab__plus--active': isShow}"></view>
  33. <view class="fab-circle-h" :class="{'uni-fab__plus--active': isShow}"></view>
  34. </view>
  35. </view>
  36. </template>
  37. <script>
  38. let platform = 'other'
  39. // #ifdef APP-NVUE
  40. platform = uni.getSystemInfoSync().platform
  41. // #endif
  42. /**
  43. * Fab 悬浮按钮
  44. * @description 点击可展开一个图形按钮菜单
  45. * @tutorial https://ext.dcloud.net.cn/plugin?id=144
  46. * @property {Object} pattern 可选样式配置项
  47. * @property {Object} horizontal = [left | right] 水平对齐方式
  48. * @value left 左对齐
  49. * @value right 右对齐
  50. * @property {Object} vertical = [bottom | top] 垂直对齐方式
  51. * @value bottom 下对齐
  52. * @value top 上对齐
  53. * @property {Object} direction = [horizontal | vertical] 展开菜单显示方式
  54. * @value horizontal 水平显示
  55. * @value vertical 垂直显示
  56. * @property {Array} content 展开菜单内容配置项
  57. * @property {Boolean} popMenu 是否使用弹出菜单
  58. * @event {Function} trigger 展开菜单点击事件,返回点击信息
  59. * @event {Function} fabClick 悬浮按钮点击事件
  60. */
  61. export default {
  62. name: 'UniFab',
  63. props: {
  64. pattern: {
  65. type: Object,
  66. default () {
  67. return {}
  68. }
  69. },
  70. horizontal: {
  71. type: String,
  72. default: 'left'
  73. },
  74. vertical: {
  75. type: String,
  76. default: 'bottom'
  77. },
  78. direction: {
  79. type: String,
  80. default: 'horizontal'
  81. },
  82. content: {
  83. type: Array,
  84. default () {
  85. return []
  86. }
  87. },
  88. show: {
  89. type: Boolean,
  90. default: false
  91. },
  92. popMenu: {
  93. type: Boolean,
  94. default: true
  95. }
  96. },
  97. data() {
  98. return {
  99. fabShow: false,
  100. isShow: false,
  101. isAndroidNvue: platform === 'android',
  102. styles: {
  103. color: '#3c3e49',
  104. selectedColor: '#007AFF',
  105. backgroundColor: '#fff',
  106. buttonColor: '#3c3e49'
  107. }
  108. }
  109. },
  110. computed: {
  111. contentWidth(e) {
  112. return (this.content.length + 1) * 55 + 10 + 'px'
  113. },
  114. contentWidthMin() {
  115. return 55 + 'px'
  116. },
  117. // 动态计算宽度
  118. boxWidth() {
  119. return this.getPosition(3, 'horizontal')
  120. },
  121. // 动态计算高度
  122. boxHeight() {
  123. return this.getPosition(3, 'vertical')
  124. },
  125. // 计算左下位置
  126. leftBottom() {
  127. return this.getPosition(0, 'left', 'bottom')
  128. },
  129. // 计算右下位置
  130. rightBottom() {
  131. return this.getPosition(0, 'right', 'bottom')
  132. },
  133. // 计算左上位置
  134. leftTop() {
  135. return this.getPosition(0, 'left', 'top')
  136. },
  137. rightTop() {
  138. return this.getPosition(0, 'right', 'top')
  139. },
  140. flexDirectionStart() {
  141. return this.getPosition(1, 'vertical', 'top')
  142. },
  143. flexDirectionEnd() {
  144. return this.getPosition(1, 'vertical', 'bottom')
  145. },
  146. horizontalLeft() {
  147. return this.getPosition(2, 'horizontal', 'left')
  148. },
  149. horizontalRight() {
  150. return this.getPosition(2, 'horizontal', 'right')
  151. }
  152. },
  153. watch: {
  154. pattern(newValue, oldValue) {
  155. //console.log(JSON.stringify(newValue))
  156. this.styles = Object.assign({}, this.styles, newValue)
  157. }
  158. },
  159. created() {
  160. this.isShow = this.show
  161. if (this.top === 0) {
  162. this.fabShow = true
  163. }
  164. // 初始化样式
  165. this.styles = Object.assign({}, this.styles, this.pattern)
  166. },
  167. methods: {
  168. _onClick() {
  169. this.$emit('fabClick')
  170. if (!this.popMenu) {
  171. return
  172. }
  173. this.isShow = !this.isShow
  174. },
  175. open() {
  176. this.isShow = true
  177. },
  178. close() {
  179. this.isShow = false
  180. },
  181. /**
  182. * 按钮点击事件
  183. */
  184. _onItemClick(index, item) {
  185. this.$emit('trigger', {
  186. index,
  187. item
  188. })
  189. },
  190. /**
  191. * 获取 位置信息
  192. */
  193. getPosition(types, paramA, paramB) {
  194. if (types === 0) {
  195. return this.horizontal === paramA && this.vertical === paramB
  196. } else if (types === 1) {
  197. return this.direction === paramA && this.vertical === paramB
  198. } else if (types === 2) {
  199. return this.direction === paramA && this.horizontal === paramB
  200. } else {
  201. return this.isShow && this.direction === paramA ? this.contentWidth : this.contentWidthMin
  202. }
  203. }
  204. }
  205. }
  206. </script>
  207. <style scoped>
  208. .uni-fab {
  209. position: fixed;
  210. /* #ifndef APP-NVUE */
  211. display: flex;
  212. /* #endif */
  213. justify-content: center;
  214. align-items: center;
  215. z-index: 10;
  216. }
  217. .uni-fab--active {
  218. opacity: 1;
  219. }
  220. .uni-fab--leftBottom {
  221. left: 5px;
  222. bottom: 20px;
  223. /* #ifdef H5 */
  224. bottom: calc(20px + var(--window-bottom));
  225. /* #endif */
  226. padding: 10px;
  227. }
  228. .uni-fab--leftTop {
  229. left: 5px;
  230. top: 30px;
  231. /* #ifdef H5 */
  232. top: calc(30px + var(--window-top));
  233. /* #endif */
  234. padding: 10px;
  235. }
  236. .uni-fab--rightBottom {
  237. right: 5px;
  238. bottom: 20px;
  239. /* #ifdef H5 */
  240. bottom: calc(20px + var(--window-bottom));
  241. /* #endif */
  242. padding: 10px;
  243. }
  244. .uni-fab--rightTop {
  245. right: 5px;
  246. top: 30px;
  247. /* #ifdef H5 */
  248. top: calc(30px + var(--window-top));
  249. /* #endif */
  250. padding: 10px;
  251. }
  252. .uni-fab__circle {
  253. position: fixed;
  254. /* #ifndef APP-NVUE */
  255. display: flex;
  256. /* #endif */
  257. justify-content: center;
  258. align-items: center;
  259. width: 55px;
  260. height: 55px;
  261. background-color: #3c3e49;
  262. border-radius: 55px;
  263. z-index: 11;
  264. }
  265. .uni-fab__circle--leftBottom {
  266. left: 15px;
  267. bottom: 30px;
  268. /* #ifdef H5 */
  269. bottom: calc(30px + var(--window-bottom));
  270. /* #endif */
  271. }
  272. .uni-fab__circle--leftTop {
  273. left: 15px;
  274. top: 40px;
  275. /* #ifdef H5 */
  276. top: calc(40px + var(--window-top));
  277. /* #endif */
  278. }
  279. .uni-fab__circle--rightBottom {
  280. right: 15px;
  281. bottom: 30px;
  282. /* #ifdef H5 */
  283. bottom: calc(30px + var(--window-bottom));
  284. /* #endif */
  285. }
  286. .uni-fab__circle--rightTop {
  287. right: 15px;
  288. top: 40px;
  289. /* #ifdef H5 */
  290. top: calc(40px + var(--window-top));
  291. /* #endif */
  292. }
  293. .uni-fab__circle--left {
  294. left: 0;
  295. }
  296. .uni-fab__circle--right {
  297. right: 0;
  298. }
  299. .uni-fab__circle--top {
  300. top: 0;
  301. }
  302. .uni-fab__circle--bottom {
  303. bottom: 0;
  304. }
  305. .uni-fab__plus {
  306. font-weight: bold;
  307. }
  308. .fab-circle-v {
  309. position: absolute;
  310. width: 3px;
  311. height: 31px;
  312. left: 26px;
  313. top: 12px;
  314. background-color: white;
  315. transform: rotate(0deg);
  316. transition: transform 0.3s;
  317. }
  318. .fab-circle-h {
  319. position: absolute;
  320. width: 31px;
  321. height: 3px;
  322. left: 12px;
  323. top: 26px;
  324. background-color: white;
  325. transform: rotate(0deg);
  326. transition: transform 0.3s;
  327. }
  328. .uni-fab__plus--active {
  329. transform: rotate(135deg);
  330. }
  331. .uni-fab__content {
  332. /* #ifndef APP-NVUE */
  333. box-sizing: border-box;
  334. display: flex;
  335. /* #endif */
  336. flex-direction: row;
  337. border-radius: 55px;
  338. overflow: hidden;
  339. transition-property: width, height;
  340. transition-duration: 0.2s;
  341. width: 55px;
  342. border-color: #DDDDDD;
  343. border-width: 1rpx;
  344. border-style: solid;
  345. }
  346. .uni-fab__content--other-platform {
  347. border-width: 0px;
  348. box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.2);
  349. }
  350. .uni-fab__content--left {
  351. justify-content: flex-start;
  352. }
  353. .uni-fab__content--right {
  354. justify-content: flex-end;
  355. }
  356. .uni-fab__content--flexDirection {
  357. flex-direction: column;
  358. justify-content: flex-end;
  359. }
  360. .uni-fab__content--flexDirectionStart {
  361. flex-direction: column;
  362. justify-content: flex-start;
  363. }
  364. .uni-fab__content--flexDirectionEnd {
  365. flex-direction: column;
  366. justify-content: flex-end;
  367. }
  368. .uni-fab__item {
  369. /* #ifndef APP-NVUE */
  370. display: flex;
  371. /* #endif */
  372. flex-direction: column;
  373. justify-content: center;
  374. align-items: center;
  375. width: 55px;
  376. height: 55px;
  377. opacity: 0;
  378. transition: opacity 0.2s;
  379. }
  380. .uni-fab__item--active {
  381. opacity: 1;
  382. }
  383. .uni-fab__item-image {
  384. width: 25px;
  385. height: 25px;
  386. margin-bottom: 3px;
  387. }
  388. .uni-fab__item-text {
  389. color: #FFFFFF;
  390. font-size: 12px;
  391. }
  392. .uni-fab__item--first {
  393. width: 55px;
  394. }
  395. </style>