475 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Vue
		
	
	
			
		
		
	
	
			475 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Vue
		
	
	
<template>
 | 
						|
  <view
 | 
						|
    class="ui-tab"
 | 
						|
    ref="tabRef"
 | 
						|
    :id="'tab-' + vm.uid"
 | 
						|
    :class="[
 | 
						|
      props.ui,
 | 
						|
      props.tpl,
 | 
						|
      props.bg,
 | 
						|
      props.align,
 | 
						|
      { 'ui-tab-inline': props.inline },
 | 
						|
      { 'ui-tab-scrolls': props.scroll },
 | 
						|
    ]"
 | 
						|
  >
 | 
						|
    <block v-if="scroll">
 | 
						|
      <view class="ui-tab-scroll-warp">
 | 
						|
        <scroll-view
 | 
						|
          scroll-x="true"
 | 
						|
          class="ui-tab-scroll"
 | 
						|
          :scroll-left="state.curValue > 1 ? state.tabNodeList[state.curValue - 1].left : 0"
 | 
						|
          scroll-with-animation
 | 
						|
          :style="{ width: `${state.content.width}px` }"
 | 
						|
        >
 | 
						|
          <view class="ss-flex ss-col-center">
 | 
						|
            <su-tab-item
 | 
						|
              v-for="(item, index) in props.tab"
 | 
						|
              :data="item"
 | 
						|
              :index="index"
 | 
						|
              :key="index"
 | 
						|
              @up="upitem"
 | 
						|
              @tap.native="click(index, item)"
 | 
						|
            ></su-tab-item>
 | 
						|
            <view
 | 
						|
              class="ui-tab-mark-warp"
 | 
						|
              :class="[{ over: state.over }]"
 | 
						|
              :style="[{ left: state.markLeft + 'px' }, { width: state.markWidth + 'px' }]"
 | 
						|
            >
 | 
						|
              <view
 | 
						|
                class="ui-tab-mark"
 | 
						|
                :class="[props.mark, { 'ui-btn': props.tpl == 'btn' || props.tpl == 'subtitle' }]"
 | 
						|
                :style="[
 | 
						|
                  {
 | 
						|
                    background:
 | 
						|
                      props.tpl == 'btn' || props.tpl == 'subtitle' ? titleStyle.activeBg : 'none',
 | 
						|
                  },
 | 
						|
                ]"
 | 
						|
              ></view>
 | 
						|
            </view>
 | 
						|
          </view>
 | 
						|
        </scroll-view>
 | 
						|
      </view>
 | 
						|
    </block>
 | 
						|
    <block v-else>
 | 
						|
      <su-tab-item
 | 
						|
        v-for="(item, index) in props.tab"
 | 
						|
        :data="item"
 | 
						|
        :index="index"
 | 
						|
        :key="index"
 | 
						|
        @up="upitem"
 | 
						|
        @tap.native="click(index, item)"
 | 
						|
      ></su-tab-item>
 | 
						|
      <view
 | 
						|
        class="ui-tab-mark-warp"
 | 
						|
        :class="[{ over: state.over }]"
 | 
						|
        :style="[{ left: state.markLeft + 'px' }, { width: state.markWidth + 'px' }]"
 | 
						|
      >
 | 
						|
        <view
 | 
						|
          class="ui-tab-mark"
 | 
						|
          :class="[props.mark, { 'ui-btn': props.tpl == 'btn' || props.tpl == 'subtitle' }]"
 | 
						|
        ></view>
 | 
						|
      </view>
 | 
						|
    </block>
 | 
						|
  </view>
 | 
						|
</template>
 | 
						|
 | 
						|
<script>
 | 
						|
  export default {
 | 
						|
    name: 'SuTab',
 | 
						|
  };
 | 
						|
</script>
 | 
						|
 | 
						|
<script setup>
 | 
						|
  /**
 | 
						|
   * 基础组件 - suTab
 | 
						|
   */
 | 
						|
 | 
						|
  import {
 | 
						|
    toRef,
 | 
						|
    ref,
 | 
						|
    reactive,
 | 
						|
    unref,
 | 
						|
    onMounted,
 | 
						|
    nextTick,
 | 
						|
    getCurrentInstance,
 | 
						|
    provide,
 | 
						|
  } from 'vue';
 | 
						|
  const vm = getCurrentInstance();
 | 
						|
 | 
						|
  // 数据
 | 
						|
  const state = reactive({
 | 
						|
    curValue: 0,
 | 
						|
    tabNodeList: [],
 | 
						|
    scrollLeft: 0,
 | 
						|
    markLeft: 0,
 | 
						|
    markWidth: 0,
 | 
						|
    content: {
 | 
						|
      width: 100,
 | 
						|
    },
 | 
						|
    over: false,
 | 
						|
  });
 | 
						|
 | 
						|
  const tabRef = ref(null);
 | 
						|
  // 参数
 | 
						|
  const props = defineProps({
 | 
						|
    modelValue: {
 | 
						|
      type: Number,
 | 
						|
      default: 0,
 | 
						|
    },
 | 
						|
    ui: {
 | 
						|
      type: String,
 | 
						|
      default: '',
 | 
						|
    },
 | 
						|
    bg: {
 | 
						|
      type: String,
 | 
						|
      default: '',
 | 
						|
    },
 | 
						|
    tab: {
 | 
						|
      type: Array,
 | 
						|
      default() {
 | 
						|
        return [];
 | 
						|
      },
 | 
						|
    },
 | 
						|
    // line dot long,subtitle,trapezoid
 | 
						|
    tpl: {
 | 
						|
      type: String,
 | 
						|
      default: 'line',
 | 
						|
    },
 | 
						|
    mark: {
 | 
						|
      type: String,
 | 
						|
      default: '',
 | 
						|
    },
 | 
						|
    align: {
 | 
						|
      type: String,
 | 
						|
      default: '',
 | 
						|
    },
 | 
						|
    curColor: {
 | 
						|
      type: String,
 | 
						|
      default: 'ui-TC',
 | 
						|
    },
 | 
						|
    defaultColor: {
 | 
						|
      type: String,
 | 
						|
      default: 'ui-TC',
 | 
						|
    },
 | 
						|
    scroll: {
 | 
						|
      type: Boolean,
 | 
						|
      default: false,
 | 
						|
    },
 | 
						|
    inline: {
 | 
						|
      type: Boolean,
 | 
						|
      default: false,
 | 
						|
    },
 | 
						|
    titleStyle: {
 | 
						|
      type: Object,
 | 
						|
      default: () => ({
 | 
						|
        activeBg: '#DA2B10',
 | 
						|
        activeColor: '#FEFEFE',
 | 
						|
        color: '#D70000',
 | 
						|
      }),
 | 
						|
    },
 | 
						|
    subtitleStyle: {
 | 
						|
      type: Object,
 | 
						|
      default: () => ({
 | 
						|
        activeColor: '#333',
 | 
						|
        color: '#C42222',
 | 
						|
      }),
 | 
						|
    },
 | 
						|
  });
 | 
						|
 | 
						|
  const emits = defineEmits(['update:modelValue', 'change']);
 | 
						|
 | 
						|
  onMounted(() => {
 | 
						|
    state.curValue = props.modelValue;
 | 
						|
    setCurValue(props.modelValue);
 | 
						|
    nextTick(() => {
 | 
						|
      computedQuery();
 | 
						|
    });
 | 
						|
    uni.onWindowResize((res) => {
 | 
						|
      computedQuery();
 | 
						|
    });
 | 
						|
  });
 | 
						|
 | 
						|
  const computedQuery = () => {
 | 
						|
    uni.createSelectorQuery()
 | 
						|
      .in(vm)
 | 
						|
      .select('#tab-' + vm.uid)
 | 
						|
      .boundingClientRect((data) => {
 | 
						|
        if (data != null) {
 | 
						|
          if (data.left == 0 && data.right == 0) {
 | 
						|
            // setTimeout(() => {
 | 
						|
            computedQuery();
 | 
						|
            // }, 300);
 | 
						|
          } else {
 | 
						|
            state.content = data;
 | 
						|
            setTimeout(() => {
 | 
						|
              state.over = true;
 | 
						|
            }, 300);
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          console.log('tab-' + vm.uid + ' data error');
 | 
						|
        }
 | 
						|
      })
 | 
						|
      .exec();
 | 
						|
  };
 | 
						|
 | 
						|
  const setCurValue = (value) => {
 | 
						|
    if (value == state.curValue) return;
 | 
						|
    state.curValue = value;
 | 
						|
    computedMark();
 | 
						|
  };
 | 
						|
 | 
						|
  const click = (index, item) => {
 | 
						|
    setCurValue(index);
 | 
						|
    emits('update:modelValue', index);
 | 
						|
    emits('change', {
 | 
						|
      index: index,
 | 
						|
      data: item,
 | 
						|
    });
 | 
						|
  };
 | 
						|
 | 
						|
  const upitem = (index, e) => {
 | 
						|
    state.tabNodeList[index] = e;
 | 
						|
    if (index == state.curValue) {
 | 
						|
      computedMark();
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  const computedMark = () => {
 | 
						|
    if (state.tabNodeList.length == 0) return;
 | 
						|
    let left = 0;
 | 
						|
    let list = unref(state.tabNodeList);
 | 
						|
    let cur = state.curValue;
 | 
						|
    state.markLeft = list[cur].left - state.content.left;
 | 
						|
    state.markWidth = list[cur].width;
 | 
						|
  };
 | 
						|
 | 
						|
  const computedScroll = () => {
 | 
						|
    if (state.curValue == 0 || state.curValue == state.tabNodeList.length - 1) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    let i = 0;
 | 
						|
    let left = 0;
 | 
						|
    let list = state.tabNodeList;
 | 
						|
    for (i in list) {
 | 
						|
      if (i == state.curValue && i != 0) {
 | 
						|
        left = left - list[i - 1].width;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      left = left + list[i].width;
 | 
						|
    }
 | 
						|
    state.scrollLeft = left;
 | 
						|
  };
 | 
						|
 | 
						|
  provide('suTabProvide', {
 | 
						|
    props,
 | 
						|
    curValue: toRef(state, 'curValue'),
 | 
						|
  });
 | 
						|
</script>
 | 
						|
 | 
						|
<style lang="scss">
 | 
						|
  .ui-tab {
 | 
						|
    position: relative;
 | 
						|
    display: flex;
 | 
						|
    height: 4em;
 | 
						|
    align-items: center;
 | 
						|
 | 
						|
    &.ui-tab-scrolls {
 | 
						|
      width: 100%;
 | 
						|
      /* #ifdef MP-WEIXIN */
 | 
						|
      padding-bottom: 10px;
 | 
						|
      /* #endif */
 | 
						|
      .ui-tab-scroll-warp {
 | 
						|
        overflow: hidden;
 | 
						|
        height: inherit;
 | 
						|
        width: 100%;
 | 
						|
        .ui-tab-scroll {
 | 
						|
          position: relative;
 | 
						|
          display: block;
 | 
						|
          white-space: nowrap;
 | 
						|
          overflow: auto;
 | 
						|
          min-height: 4em;
 | 
						|
          line-height: 4em;
 | 
						|
          width: 100% !important;
 | 
						|
          .ui-tab-mark-warp {
 | 
						|
            display: flex;
 | 
						|
            align-items: top;
 | 
						|
            justify-content: center;
 | 
						|
            .ui-tab-mark.ui-btn {
 | 
						|
              /* #ifndef MP-WEIXIN */
 | 
						|
              height: 2em;
 | 
						|
              width: calc(100% - 0.6em);
 | 
						|
              margin-top: 4px;
 | 
						|
              /* #endif */
 | 
						|
              /* #ifdef MP-WEIXIN */
 | 
						|
              height: 2em;
 | 
						|
              width: calc(100% - 0.6em);
 | 
						|
              margin-top: 4px;
 | 
						|
              /* #endif */
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    .ui-tab-mark-warp {
 | 
						|
      color: inherit;
 | 
						|
      position: absolute;
 | 
						|
      top: 0;
 | 
						|
      height: 100%;
 | 
						|
      z-index: 0;
 | 
						|
 | 
						|
      &.over {
 | 
						|
        transition: 0.3s;
 | 
						|
      }
 | 
						|
 | 
						|
      .ui-tab-mark {
 | 
						|
        color: var(--ui-BG-Main);
 | 
						|
        height: 100%;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    &.line {
 | 
						|
      .ui-tab-mark {
 | 
						|
        border-bottom: 2px solid currentColor;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    &.topline {
 | 
						|
      .ui-tab-mark {
 | 
						|
        border-top: 2px solid currentColor;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    &.dot {
 | 
						|
      .ui-tab-mark::after {
 | 
						|
        content: '';
 | 
						|
        width: 0.5em;
 | 
						|
        height: 0.5em;
 | 
						|
        background-color: currentColor;
 | 
						|
        border-radius: 50%;
 | 
						|
        display: block;
 | 
						|
        position: absolute;
 | 
						|
        bottom: 0.3em;
 | 
						|
        left: 0;
 | 
						|
        right: 0;
 | 
						|
        margin: auto;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    &.long {
 | 
						|
      .ui-tab-mark::after {
 | 
						|
        content: '';
 | 
						|
        width: 2em;
 | 
						|
        height: 0.35em;
 | 
						|
        background-color: currentColor;
 | 
						|
        border-radius: 5em;
 | 
						|
        display: block;
 | 
						|
        position: absolute;
 | 
						|
        bottom: 0.3em;
 | 
						|
        left: 0;
 | 
						|
        right: 0;
 | 
						|
        margin: auto;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    &.trapezoid {
 | 
						|
      .ui-tab-mark::after {
 | 
						|
        content: '';
 | 
						|
        width: calc(100% - 2em);
 | 
						|
        height: 0.35em;
 | 
						|
        background-color: currentColor;
 | 
						|
        border-radius: 5em 5em 0 0;
 | 
						|
        display: block;
 | 
						|
        position: absolute;
 | 
						|
        bottom: 0;
 | 
						|
        left: 0;
 | 
						|
        right: 0;
 | 
						|
        margin: auto;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    &.btn {
 | 
						|
      .ui-tab-mark-warp {
 | 
						|
        display: flex;
 | 
						|
        align-items: center;
 | 
						|
        justify-content: center;
 | 
						|
 | 
						|
        .ui-tab-mark.ui-btn {
 | 
						|
          height: calc(100% - 1.6em);
 | 
						|
          width: calc(100% - 0.6em);
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      &.sm .ui-tab-mark.ui-btn {
 | 
						|
        height: calc(100% - 2px);
 | 
						|
        width: calc(100% - 2px);
 | 
						|
        border-radius: #{$radius - 2};
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    &.subtitle {
 | 
						|
      .ui-tab-mark-warp {
 | 
						|
        display: flex;
 | 
						|
        align-items: top;
 | 
						|
        justify-content: center;
 | 
						|
        padding-top: 0.6em;
 | 
						|
 | 
						|
        .ui-tab-mark.ui-btn {
 | 
						|
          height: calc(100% - 2.8em);
 | 
						|
          width: calc(100% - 0.6em);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    &.ui-tab-inline {
 | 
						|
      display: inline-flex;
 | 
						|
      height: 3.5em;
 | 
						|
 | 
						|
      &.ui-tab-scrolls {
 | 
						|
        .ui-tab-scroll {
 | 
						|
          height: calc(3.5em + 17px);
 | 
						|
          line-height: 3.5em;
 | 
						|
 | 
						|
          .ui-tab-mark-warp {
 | 
						|
            height: 3.5em;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      &.btn {
 | 
						|
        .ui-tab-mark-warp {
 | 
						|
          .ui-tab-mark.ui-btn {
 | 
						|
            height: calc(100% - 10px);
 | 
						|
            width: calc(100% - 10px);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    &.sm {
 | 
						|
      height: 70rpx !important;
 | 
						|
 | 
						|
      &.ui-tab-inline {
 | 
						|
        height: 70rpx;
 | 
						|
 | 
						|
        &.ui-tab-scrolls {
 | 
						|
          .ui-tab-scroll {
 | 
						|
            height: calc(70rpx + 17px);
 | 
						|
            line-height: 70rpx;
 | 
						|
 | 
						|
            .ui-tab-mark-warp {
 | 
						|
              height: 70rpx;
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        &.btn .ui-tab-mark.ui-btn {
 | 
						|
          height: calc(100% - 2px);
 | 
						|
          width: calc(100% - 2px);
 | 
						|
          border-radius: #{$radius - 2};
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
</style>
 |