You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

315 lines
9.6 KiB

3 years ago
  1. <template>
  2. <view class="">
  3. <view class="u-navbar" :style="[navbarStyle]" :class="{ 'u-navbar-fixed': isFixed, 'u-border-bottom': borderBottom }">
  4. <view class="u-status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
  5. <view class="u-navbar-inner" :style="[navbarInnerStyle]">
  6. <view class="u-back-wrap" v-if="isBack" @tap="goBack">
  7. <view class="u-icon-wrap">
  8. <u-icon :name="backIconName" :color="backIconColor" :size="backIconSize"></u-icon>
  9. </view>
  10. <view class="u-icon-wrap u-back-text u-line-1" v-if="backText" :style="[backTextStyle]">{{ backText }}</view>
  11. </view>
  12. <view class="u-navbar-content-title" v-if="title" :style="[titleStyle]">
  13. <view
  14. class="u-title u-line-1"
  15. :style="{
  16. color: titleColor,
  17. fontSize: titleSize + 'rpx',
  18. fontWeight: titleBold ? 'bold' : 'normal'
  19. }">
  20. {{ title }}
  21. </view>
  22. </view>
  23. <view class="u-slot-content">
  24. <slot></slot>
  25. </view>
  26. <view class="u-slot-right">
  27. <slot name="right"></slot>
  28. </view>
  29. </view>
  30. </view>
  31. <!-- 解决fixed定位后导航栏塌陷的问题 -->
  32. <view class="u-navbar-placeholder" v-if="isFixed && !immersive" :style="{ width: '100%', height: Number(navbarHeight) + statusBarHeight + 'px' }"></view>
  33. </view>
  34. </template>
  35. <script>
  36. // 获取系统状态栏的高度
  37. let systemInfo = uni.getSystemInfoSync();
  38. let menuButtonInfo = {};
  39. // 如果是小程序,获取右上角胶囊的尺寸信息,避免导航栏右侧内容与胶囊重叠(支付宝小程序非本API,尚未兼容)
  40. // #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
  41. menuButtonInfo = uni.getMenuButtonBoundingClientRect();
  42. // #endif
  43. /**
  44. * navbar 自定义导航栏
  45. * @description 此组件一般用于在特殊情况下需要自定义导航栏的时候用到一般建议使用uniapp自带的导航栏
  46. * @tutorial https://www.uviewui.com/components/navbar.html
  47. * @property {String Number} height 导航栏高度(不包括状态栏高度在内内部自动加上)注意这里的单位是px默认44
  48. * @property {String} back-icon-color 左边返回图标的颜色默认#606266
  49. * @property {String} back-icon-name 左边返回图标的名称只能为uView自带的图标默认arrow-left
  50. * @property {String Number} back-icon-size 左边返回图标的大小单位rpx默认30
  51. * @property {String} back-text 返回图标右边的辅助提示文字
  52. * @property {Object} back-text-style 返回图标右边的辅助提示文字的样式对象形式默认{ color: '#606266' }
  53. * @property {String} title 导航栏标题如设置为空字符将会隐藏标题占位区域
  54. * @property {String Number} title-width 导航栏标题的最大宽度内容超出会以省略号隐藏单位rpx默认250
  55. * @property {String} title-color 标题的颜色默认#606266
  56. * @property {String Number} title-size 导航栏标题字体大小单位rpx默认32
  57. * @property {Function} custom-back 自定义返回逻辑方法
  58. * @property {String Number} z-index 固定在顶部时的z-index值默认980
  59. * @property {Boolean} is-back 是否显示导航栏左边返回图标和辅助文字默认true
  60. * @property {Object} background 导航栏背景设置见官网说明默认{ background: '#ffffff' }
  61. * @property {Boolean} is-fixed 导航栏是否固定在顶部默认true
  62. * @property {Boolean} immersive 沉浸式允许fixed定位后导航栏塌陷仅fixed定位下生效默认false
  63. * @property {Boolean} border-bottom 导航栏底部是否显示下边框如定义了较深的背景颜色可取消此值默认true
  64. * @example <u-navbar back-text="返回" title="剑未配妥,出门已是江湖"></u-navbar>
  65. */
  66. export default {
  67. name: "u-navbar",
  68. props: {
  69. // 导航栏高度,单位px,非rpx
  70. height: {
  71. type: [String, Number],
  72. default: ''
  73. },
  74. // 返回箭头的颜色
  75. backIconColor: {
  76. type: String,
  77. default: '#606266'
  78. },
  79. // 左边返回的图标
  80. backIconName: {
  81. type: String,
  82. default: 'nav-back'
  83. },
  84. // 左边返回图标的大小,rpx
  85. backIconSize: {
  86. type: [String, Number],
  87. default: '44'
  88. },
  89. // 返回的文字提示
  90. backText: {
  91. type: String,
  92. default: ''
  93. },
  94. // 返回的文字的 样式
  95. backTextStyle: {
  96. type: Object,
  97. default () {
  98. return {
  99. color: '#606266'
  100. }
  101. }
  102. },
  103. // 导航栏标题
  104. title: {
  105. type: String,
  106. default: ''
  107. },
  108. // 标题的宽度,如果需要自定义右侧内容,且右侧内容很多时,可能需要减少这个宽度,单位rpx
  109. titleWidth: {
  110. type: [String, Number],
  111. default: '250'
  112. },
  113. // 标题的颜色
  114. titleColor: {
  115. type: String,
  116. default: '#606266'
  117. },
  118. // 标题字体是否加粗
  119. titleBold: {
  120. type: Boolean,
  121. default: false
  122. },
  123. // 标题的字体大小
  124. titleSize: {
  125. type: [String, Number],
  126. default: 32
  127. },
  128. isBack: {
  129. type: [Boolean, String],
  130. default: true
  131. },
  132. // 对象形式,因为用户可能定义一个纯色,或者线性渐变的颜色
  133. background: {
  134. type: Object,
  135. default () {
  136. return {
  137. background: '#ffffff'
  138. }
  139. }
  140. },
  141. // 导航栏是否固定在顶部
  142. isFixed: {
  143. type: Boolean,
  144. default: true
  145. },
  146. // 是否沉浸式,允许fixed定位后导航栏塌陷,仅fixed定位下生效
  147. immersive: {
  148. type: Boolean,
  149. default: false
  150. },
  151. // 是否显示导航栏的下边框
  152. borderBottom: {
  153. type: Boolean,
  154. default: true
  155. },
  156. zIndex: {
  157. type: [String, Number],
  158. default: ''
  159. },
  160. // 自定义返回逻辑
  161. customBack: {
  162. type: Function,
  163. default: null
  164. }
  165. },
  166. data() {
  167. return {
  168. menuButtonInfo: menuButtonInfo,
  169. statusBarHeight: systemInfo.statusBarHeight
  170. };
  171. },
  172. computed: {
  173. // 导航栏内部盒子的样式
  174. navbarInnerStyle() {
  175. let style = {};
  176. // 导航栏宽度,如果在小程序下,导航栏宽度为胶囊的左边到屏幕左边的距离
  177. style.height = this.navbarHeight + 'px';
  178. // // 如果是各家小程序,导航栏内部的宽度需要减少右边胶囊的宽度
  179. // #ifdef MP
  180. let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left;
  181. style.marginRight = rightButtonWidth + 'px';
  182. // #endif
  183. return style;
  184. },
  185. // 整个导航栏的样式
  186. navbarStyle() {
  187. let style = {};
  188. style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.navbar;
  189. // 合并用户传递的背景色对象
  190. Object.assign(style, this.background);
  191. return style;
  192. },
  193. // 导航中间的标题的样式
  194. titleStyle() {
  195. let style = {};
  196. // #ifndef MP
  197. style.left = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px';
  198. style.right = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px';
  199. // #endif
  200. // #ifdef MP
  201. // 此处是为了让标题显示区域即使在小程序有右侧胶囊的情况下也能处于屏幕的中间,是通过绝对定位实现的
  202. let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left;
  203. style.left = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px';
  204. style.right = rightButtonWidth - (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + rightButtonWidth +
  205. 'px';
  206. // #endif
  207. style.width = uni.upx2px(this.titleWidth) + 'px';
  208. return style;
  209. },
  210. // 转换字符数值为真正的数值
  211. navbarHeight() {
  212. // #ifdef APP-PLUS || H5
  213. return this.height ? this.height : 44;
  214. // #endif
  215. // #ifdef MP
  216. // 小程序特别处理,让导航栏高度 = 胶囊高度 + 两倍胶囊顶部与状态栏底部的距离之差(相当于同时获得了导航栏底部与胶囊底部的距离)
  217. // 此方法有缺陷,暂不用(会导致少了几个px),采用直接固定值的方式
  218. // return menuButtonInfo.height + (menuButtonInfo.top - this.statusBarHeight) * 2;//导航高度
  219. let height = systemInfo.platform == 'ios' ? 44 : 48;
  220. return this.height ? this.height : height;
  221. // #endif
  222. }
  223. },
  224. created() {},
  225. methods: {
  226. goBack() {
  227. // 如果自定义了点击返回按钮的函数,则执行,否则执行返回逻辑
  228. if (typeof this.customBack === 'function') {
  229. // 在微信,支付宝等环境(H5正常),会导致父组件定义的customBack()函数体中的this变成子组件的this
  230. // 通过bind()方法,绑定父组件的this,让this.customBack()的this为父组件的上下文
  231. this.customBack.bind(this.$u.$parent.call(this))();
  232. } else {
  233. uni.navigateBack();
  234. }
  235. }
  236. }
  237. };
  238. </script>
  239. <style scoped lang="scss">
  240. @import "../../libs/css/style.components.scss";
  241. .u-navbar {
  242. width: 100%;
  243. }
  244. .u-navbar-fixed {
  245. position: fixed;
  246. left: 0;
  247. right: 0;
  248. top: 0;
  249. z-index: 991;
  250. }
  251. .u-status-bar {
  252. width: 100%;
  253. }
  254. .u-navbar-inner {
  255. @include vue-flex;
  256. justify-content: space-between;
  257. position: relative;
  258. align-items: center;
  259. }
  260. .u-back-wrap {
  261. @include vue-flex;
  262. align-items: center;
  263. flex: 1;
  264. flex-grow: 0;
  265. padding: 14rpx 14rpx 14rpx 24rpx;
  266. }
  267. .u-back-text {
  268. padding-left: 4rpx;
  269. font-size: 30rpx;
  270. }
  271. .u-navbar-content-title {
  272. @include vue-flex;
  273. align-items: center;
  274. justify-content: center;
  275. flex: 1;
  276. position: absolute;
  277. left: 0;
  278. right: 0;
  279. height: 60rpx;
  280. text-align: center;
  281. flex-shrink: 0;
  282. }
  283. .u-navbar-centent-slot {
  284. flex: 1;
  285. }
  286. .u-title {
  287. line-height: 60rpx;
  288. font-size: 32rpx;
  289. flex: 1;
  290. }
  291. .u-navbar-right {
  292. flex: 1;
  293. @include vue-flex;
  294. align-items: center;
  295. justify-content: flex-end;
  296. }
  297. .u-slot-content {
  298. flex: 1;
  299. @include vue-flex;
  300. align-items: center;
  301. }
  302. </style>