uni-fab.vue 11 KB

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