uni-fab.vue 12 KB

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