<template>
  <view class="">
    <view
      class="mix-navbar"
      :style="[navbarStyle]"
      :class="{ 'mix-navbar-fixed': isFixed, 'mix-border-bottom': borderBottom }"
      v-if="transparentTitle == 'none' || opacity > threshold"
    >
      <view class="mix-status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
      <view class="mix-navbar-inner" :style="[navbarInnerStyle]">
        <view class="mix-back-wrap" v-if="isBack" @tap="goBack">
          <view class="mix-icon-wrap">
            <mix-icon class="mix-navbar-back-icon" :name="backIconName" :color="backIconColor" :size="backIconSize"></mix-icon>
          </view>
          <view class="mix-icon-wrap mix-back-text mix-line" v-if="backText" :style="[backTextStyle]">{{ backText }}</view>
        </view>
        <view class="mix-navbar-content-title" v-if="title" :style="[titleStyle]">
          <view class="mix-title mix-line" :style="{ color: titleColor, fontSize: titleSize + 'rpx' }">
            {{ title }}
          </view>
        </view>
        <view class="mix-slot-content">
          <!-- @slot 默认插槽，自定义中间部分的内容 -->
          <slot></slot>
        </view>
        <view class="mix-slot-right">
          <!-- @slot 右侧插槽，自定义右侧部分内容 -->
          <slot name="right"></slot>
        </view>
      </view>
    </view>

    <view class="mix-navbar" :style="[navbarTransParentStyle]" :class="{ 'mix-navbar-fixed': isFixed }" v-if="transparentTitle != 'none' && opacity < endThreshold">
      <view class="mix-status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
      <view class="mix-navbar-inner" :style="[navbarInnerStyle]">
        <view class="mix-back-wrap" v-if="isBack" @tap="goBack">
          <view class="mix-icon-wrap">
            <mix-icon
              class="mix-navbar-back-icon mix-navbar-back-icon-transparent"
              :name="backIconName"
              :color="transparentTextColor"
              :size="backIconSize"
              :style="[transBackIconStyle]"
            ></mix-icon>
          </view>
        </view>
        <view class="mix-navbar-content-title" v-if="title && showTransparentText" :style="[titleStyle]">
          <view class="mix-title mix-line" :style="{ color: transparentTextColor, fontSize: titleSize + 'rpx' }">
            {{ title }}
          </view>
        </view>
        <view class="mix-slot-content"></view>
        <view class="mix-slot-right" :style="[transRightStyle]">
          <!-- @slot 透明导航栏的右侧插槽，自定义透明导航栏的右侧部分内容 -->
          <slot name="transparent-right"></slot>
        </view>
      </view>
    </view>
    <!-- 解决fixed定位后导航栏塌陷的问题 -->
    <view
      class="mix-navbar-placeholder"
      v-if="(transparentTitle == 'none' && isFixed && !immersive) || (transparentTitle != 'none' && isFixed && !transparentImmersive)"
      :style="{ width: '100%', height: Number(navbarHeight) + statusBarHeight + 'px' }"
    ></view>
  </view>
</template>

<script>
// 获取系统状态栏的高度
let systemInfo = uni.getSystemInfoSync()
let menuButtonInfo = {}
// 如果是小程序，获取右上角胶囊的尺寸信息，避免导航栏右侧内容与胶囊重叠(支付宝小程序非本API，尚未兼容)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect()
// #endif

import color from '../../libs/function/colorGradient'
/**
 * navbar 自定义导航栏
 *
 * 参考u-navbar,主要是增加了透明、渐变头部功能
 *
 * 新增prop: `transparent-title`,`transparent-immersive`,`scroll-top`,`transparent-back-icon-bg`
 *
 * 新增插槽: `transparent-right`
 *
 * @since 0.0.2
 * @displayName Navbar
 * @description 此组件一般用于在特殊情况下，需要自定义导航栏的时候用到，一般建议使用uniapp自带的导航栏。
 * @property {String | Number} height 导航栏高度(不包括状态栏高度在内，内部自动加上)，注意这里的单位是px（默认44）
 * @property {String} back-icon-color 左边返回图标的颜色（默认#606266）
 * @property {String} back-icon-name 左边返回图标的名称，只能为uView自带的图标（默认arrow-left）
 * @property {String | Number} back-icon-size 左边返回图标的大小，单位rpx（默认30）
 * @property {String} back-text 返回图标右边的辅助提示文字
 * @property {Object} back-text-style 返回图标右边的辅助提示文字的样式，对象形式（默认{ color: '#606266' }）
 * @property {String} title 导航栏标题，如设置为空字符，将会隐藏标题占位区域
 * @property {String | Number} title-width 导航栏标题的最大宽度，内容超出会以省略号隐藏，单位rpx（默认400）
 * @property {String} title-color 标题的颜色（默认#606266）
 * @property {String | Number} title-size 导航栏标题字体大小，单位rpx（默认32）
 * @property {Function} custom-back 自定义返回逻辑方法，customBack的this为父组件的上下文
 * @property {String | Number} z-index 固定在顶部时的z-index值（默认980）
 * @property {Boolean} is-back = [true|false] 是否显示导航栏左边返回图标和辅助文字（默认true）
 * @property {Object} background 导航栏背景设置，对象形式，因为用户可能定义一个纯色，或者线性渐变的颜色（默认{ background: '#ffffff' }）
 * @property {Boolean} is-fixed = [true|false] 导航栏是否固定在顶部（默认true）
 * @property {Boolean} immersive = [true|false] 沉浸式，允许fixed定位后导航栏塌陷，仅fixed定位下生效（默认false）
 * @property {Boolean} border-bottom = [true|false] 导航栏底部是否显示下边框，如定义了较深的背景颜色，可取消此值（默认true）
 * @property {String} transparent-title = [none,auto,always] 导航栏透明设置。支持 always 一直透明 / auto 滑动自适应 / none 不透明。（默认none）
 * @property {String} transparent-text-color = 导航栏透明时，图标与标题的颜色（默认#fff）
 * @property {String} show-transparent-text = [true|false] 导航栏透明时，是否显示标题（默认false）
 * @property {Boolean} transparent-immersive = [true|false] 透明导航栏时的沉浸式，允许fixed定位后导航栏塌陷，仅fixed定位下生效（默认true）
 * @property {String | Number} scroll-top 滚动条滚动距离（初始0）
 * @property {String} transparent-back-icon-bg 透明导航栏标题返回按钮图标的背景颜色,设为空表示无背景（默认#000）
 * @property {Number} scroll-ratio  渐变滚动比，在 transparentTitle='auto' 时生效，值越大渐变过程越长（默认0.45）
 * @event {Function} change 头部透明度变化时
 */
export default {
  name: 'mix-navbar',
  props: {
    /**
     * 导航栏高度(不包括状态栏高度在内，内部自动加上)，注意这里的单位是px
     */
    height: {
      type: [String, Number],
      default: '44'
    },
    /**
     * 左边返回图标的颜色
     */
    backIconColor: {
      type: String,
      default: '#606266'
    },
    /**
     * 左边返回图标的名称，只能为mix自带的图标（默认arrow-left）
     */
    backIconName: {
      type: String,
      default: 'arrow-left'
    },
    /**
     * 左边返回图标的大小，单位rpx
     */
    backIconSize: {
      type: [String, Number],
      default: '44'
    },
    /**
     * 返回图标右边的辅助提示文字
     */
    backText: {
      type: String,
      default: ''
    },
    /**
     * 返回图标右边的辅助提示文字的样式，对象形式
     */
    backTextStyle: {
      type: Object,
      default() {
        return {
          color: '#606266'
        }
      }
    },
    /**
     * 导航栏标题，如设置为空字符，将会隐藏标题占位区域
     */
    title: {
      type: String,
      default: ''
    },
    /**
     * 导航栏标题的最大宽度，内容超出会以省略号隐藏，单位rpx
     */
    titleWidth: {
      type: [String, Number],
      default: '400'
    },
    /**
     * 标题的颜色
     */
    titleColor: {
      type: String,
      default: '#606266'
    },
    /**
     * 标题的字体大小
     */
    titleSize: {
      type: [String, Number],
      default: 32
    },
    /**
     * 是否显示导航栏左边返回图标和辅助文字
     */
    isBack: {
      type: [Boolean, String],
      default: true
    },
    /**
     * 导航栏背景设置，对象形式，因为用户可能定义一个纯色，或者线性渐变的颜色
     */
    background: {
      type: Object,
      default() {
        return {
          background: '#ffffff'
        }
      }
    },
    /**
     * 导航栏是否固定在顶部
     */
    isFixed: {
      type: Boolean,
      default: true
    },
    /**
     * 沉浸式，允许fixed定位后导航栏塌陷，仅fixed定位下生效
     */
    immersive: {
      type: Boolean,
      default: false
    },
    /**
     * 导航栏底部是否显示下边框，如定义了较深的背景颜色，可取消此值
     */
    borderBottom: {
      type: Boolean,
      default: true
    },
    /**
     * 固定在顶部时的z-index值（默认980）
     */
    zIndex: {
      type: [String, Number],
      default: ''
    },
    /**
     * 自定义返回逻辑方法，customBack的this为父组件的上下文
     */
    customBack: {
      type: Function,
      default: null
    },
    //#region 透明头部
    /**
     * 导航栏透明设置。支持 always 一直透明 / auto 滑动自适应 / none 不透明
     * @values none,always,auto
     */
    transparentTitle: { type: String, default: 'none' },
    /**
     * 导航栏透明时，图标与标题的颜色
     */
    transparentTextColor: { type: String, default: '#fff' },
    /**
     * 透明头部时是否显示标题
     */
    showTransparentText: {
      type: Boolean,
      default: false
    },
    /**
     * 滚动条滚动距离（初始0）
     */
    scrollTop: { type: [Number, String], default: 0 },
    /**
     * 透明导航栏时的沉浸式，允许fixed定位后导航栏塌陷，仅fixed定位下生效
     */
    transparentImmersive: {
      type: Boolean,
      default: true
    },
    /**
     * 透明导航栏标题返回按钮图标的背景颜色,设为空表示无背景
     */
    transparentBackIconBg: { type: String, default: '#000' },
    /**
     * 渐变滚动比，在 transparentTitle='auto' 时生效，值越大渐变过程越长
     *
     * opacity = scrollTop /windowWidth * scrollRatio
     */
    scrollRatio: {
      type: Number,
      default: 0.45
    }
    //#endregion
  },
  data() {
    return {
      //#region 透明头部
      /**
       * 当前透明度
       */
      opacity: 1,
      /**
       * 不透明度最大值 0-1
       */
      maxOpacity: 1,
      /**
       * 头部显示阈值
       */
      threshold: 0.35,
      /**
       * 隐藏透明头阈值
       */
      endThreshold: 0.8,
      /**
       * 渐变滚动比，在 transparentTitle='auto' 时生效，值越大渐变过程越长
       *
       * opacity = scrollTop /windowWidth * scrollRatio
       */
      // scrollRatio: 0.45,
      /**
       * 滚动总高度,计算opacity
       */
      scrollH: 1,
      //#endregion
      menuButtonInfo: menuButtonInfo,
      statusBarHeight: systemInfo.statusBarHeight
    }
  },
  watch: {
    //#region 透明头部
    scrollTop(newValue, oldValue) {
      if (this.transparentTitle == 'auto') {
        this.opacityChange()
      }
    },
    transparentTitle(newValue, oldValue) {
      this.opacity = newValue != 'none' ? 0 : this.maxOpacity
    }
    //#endregion
  },
  computed: {
    // 导航栏内部盒子的样式
    navbarInnerStyle() {
      let style = {}

      // 导航栏宽度，如果在小程序下，导航栏宽度为胶囊的左边到屏幕左边的距离
      style.height = this.navbarHeight + 'px'
      // // 如果是各家小程序，导航栏内部的宽度需要减少右边胶囊的宽度
      // #ifdef MP
      let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left
      style.marginRight = rightButtonWidth + 'px'
      // #endif
      return style
    },
    // 整个导航栏的样式
    navbarStyle() {
      let style = {}
      style.zIndex = this.zIndex ? this.zIndex : this.$mix.zIndex.navbar
      // 合并用户传递的背景色对象
      Object.assign(style, this.background)

      //#region 透明头部
      style.opacity = this.opacity
      //#endregion

      return style
    },
    //#region 透明头部
    // 整个透明导航栏的样式
    navbarTransParentStyle() {
      let style = {}
      style.zIndex = this.zIndex ? this.zIndex + 1 : this.$mix.zIndex.navbar + 1
      style.background = 'transparent'
      // style.opacity = this.opacity
      return style
    },
    //#endregion
    // 导航中间的标题的样式
    titleStyle() {
      let style = {}
      // #ifndef MP
      style.left = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px'
      style.right = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px'
      // #endif
      // #ifdef MP
      // 此处是为了让标题显示区域即使在小程序有右侧胶囊的情况下也能处于屏幕的中间，是通过绝对定位实现的
      let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left
      style.left = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px'
      style.right = rightButtonWidth - (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + rightButtonWidth + 'px'
      // #endif
      style.width = uni.upx2px(this.titleWidth) + 'px'
      return style
    },
    // 转换字符数值为真正的数值
    navbarHeight() {
      // #ifdef APP-PLUS || H5
      return this.height ? this.height : 44
      // #endif
      // #ifdef MP
      // 小程序特别处理，让导航栏高度 = 胶囊高度 + 两倍胶囊顶部与状态栏底部的距离之差(相当于同时获得了导航栏底部与胶囊底部的距离)
      // 此方法有缺陷，暂不用(会导致少了几个px)，采用直接固定值的方式
      // return menuButtonInfo.height + (menuButtonInfo.top - this.statusBarHeight) * 2;//导航高度
      let height = systemInfo.platform == 'ios' ? 44 : 48
      return this.height ? this.height : height
      // #endif
    },
    transBackIconStyle() {
      let style = {}
      // fixed:  this.transparentBackIconBg != 'true' 微信小程序
      if (this.transparentBackIconBg && this.transparentBackIconBg != 'true') {
        let opacity = 0.5
        if (this.opacity > this.threshold) {
          opacity = opacity - (this.opacity - this.threshold)
        }
        if (opacity < 0) {
          opacity = 0
        }
        const rgb = color.hexToRgb(this.transparentBackIconBg, false).join(',')
        style.backgroundColor = `rgba(${rgb},${opacity})`
        // background-color: rgba(0, 0, 0, 0.5);
        // $mix.hexToRgb('red',false)
      }
      return style
    },
    transRightStyle() {
      let style = {}
      let opacity = 1
      if (this.opacity > this.threshold) {
        opacity = opacity - this.opacity
      }
      if (opacity < 0) {
        opacity = 0
      }
      style.opacity = opacity
      return style
    }
  },
  created() {
    //#region 透明头部
    this.opacity = this.transparentTitle != 'none' ? 0 : this.maxOpacity
    this.threshold = this.threshold > this.maxOpacity ? this.maxOpacity : this.threshold
    this.scrollH = systemInfo.windowWidth * this.scrollRatio
    //#endregion
  },
  methods: {
    //#region 透明头部
    opacityChange() {
      let scroll = this.scrollTop <= 1 ? 0 : this.scrollTop
      let opacity = scroll / this.scrollH
      if ((this.opacity >= this.maxOpacity && opacity >= this.maxOpacity) || (this.opacity == 0 && opacity == 0)) {
        return
      }
      this.opacity = opacity > this.maxOpacity ? this.maxOpacity : opacity

      /**
       * 头部透明度变化时
       * @event change
       * @property {Object} e { opacity, threshold, endThreshold }
       * @property {Number} opacity 头部的当前透明度
       * @property {Number} threshold 头部显示阈值-透明度高于该值则显示主导航
       * @property {Number} endThreshold 隐藏透明头阈值-透明度高于该值则隐藏透明导航（副头部）
       */
      this.$emit('change', {
        opacity: this.opacity,
        threshold: this.threshold,
        endThreshold: this.endThreshold
      })
    },
    //#endregion
    goBack() {
      // 如果自定义了点击返回按钮的函数，则执行，否则执行返回逻辑
      if (typeof this.customBack === 'function') {
        // 在微信，支付宝等环境(H5正常)，会导致父组件定义的customBack()函数体中的this变成子组件的this
        // 通过bind()方法，绑定父组件的this，让this.customBack()的this为父组件的上下文
        this.customBack.bind(this.$mix.$parent.call(this))()
      } else {
        uni.navigateBack()
      }
    }
  }
}
</script>

<style scoped lang="scss">
@import '../../libs/css/style.components.scss';

.mix-navbar {
  width: 100%;
}

.mix-navbar-fixed {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  z-index: 991;
}

.mix-status-bar {
  width: 100%;
}

.mix-navbar-inner {
  @include vue-flex;
  justify-content: space-between;
  position: relative;
  align-items: center;
}

.mix-back-wrap {
  @include vue-flex;
  align-items: center;
  flex: 1;
  flex-grow: 0;
  padding: 14rpx 14rpx 14rpx 24rpx;
}

.mix-back-text {
  padding-left: 4rpx;
  font-size: 30rpx;
}

.mix-navbar-content-title {
  @include vue-flex;
  align-items: center;
  justify-content: center;
  flex: 1;
  position: absolute;
  left: 0;
  right: 0;
  height: 60rpx;
  text-align: center;
  flex-shrink: 0;
}

.mix-navbar-centent-slot {
  flex: 1;
}

.mix-title {
  line-height: 60rpx;
  font-size: 32rpx;
  flex: 1;
}

.mix-navbar-right {
  flex: 1;
  @include vue-flex;
  align-items: center;
  justify-content: flex-end;
}

.mix-slot-content {
  flex: 1;
  @include vue-flex;
  align-items: center;
}
</style>

<style lang="scss" scoped>
.mix-navbar-back-icon {
  margin-right: 6rpx;
  // #ifdef MP
  padding: 4rpx 4rpx 4rpx 4rpx;
  // #endif
  // #ifndef MP
  padding: 4rpx 4rpx 4rpx 4rpx;
  // #endif
}
.mix-navbar-back-icon-transparent {
  border-radius: 50%;
}
</style>