绩效考核手机版
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.

269 lines
8.9 KiB

3 years ago
<template>
<view :id="$sUid" class="s-tabs" :class="c_class" :style="c_style">
<scroll-view
class="s-tabs__scroll"
scroll-x
:show-scrollline="false"
:scroll-with-animation="isInit && scrollWithAnimation"
:scroll-left="scrollLeft"
:class="{ 's-hide-scrollbar': !showScrollBar }"
>
<view class="s-tabs__wrap">
<view class="s-tab-list">
<view
v-for="(item, index) of c_list"
:key="index"
class="s-tab"
:class="tabClassList[index]"
:style="tabStyleList[index]"
@click="onTabClick(item, index)"
>
<slot
:scopeParams="scopeParams"
:item="item"
:index="index"
:active="isActive(index)"
>
<view class="s-tab__content">
<s-badge
v-if="hasBadge(item)"
custom-class="s-tab__badge"
:type="item.badge.type"
:value="item.badge.value"
:dot="item.badge.dot"
:background="item.badge.background"
:color="item.badge.color"
:show-zero="item.badge.showZero"
:offset="item.badge.offset"
:offset-center="false"
:max="item.badge.max"
:scale="item.badge.scale"
/>
<text class="s-tab__text" :class="{ 's-ellipsis': ellipsis }">
{{ item[labelKey] }}
</text>
</view>
</slot>
</view>
</view>
<view v-if="show_line" class="s-tabs__line" :style="line_style"></view>
</view>
</scroll-view>
</view>
</template>
<script>
import componentMixin from '../../mixins/componentMixin';
import toValidListData from '../../utils/toValidListData';
/**
* s-tabs 标签
* @description 标签组件主要功能 选中项居中显示横条跟随
* @property {Array} list 标签数据 [{label:'Tab1', value:1, disabled:true}]
* @property {Number|String} height 标签栏高度
* @property {Number|String} value v-model双向绑定valueKey为空时根据下标匹配默认为空
* @property {String} labelKey 标签名称属性字段默认 label
* @property {String} valueKey 标签匹配属性字段设值后则开启value和属性值匹配
* @property {String} background 标签栏背景色
* @property {Boolean} activeBold 标签选中是否加粗
* @property {Number|String} fontSize 标签文字大小
* @property {String} color 标签颜色
* @property {String} activeColor 标签选中颜色
* @property {String} disabledColor 标签禁用颜色
* @property {Boolean} ellipsis 标签内容超出是否隐藏显示...
* @property {String|Object} tabStyle 标签样式
* @property {String|Object} activeTabStyle 标签选中样式
* @property {Boolean} showLine 是否显示横条
* @property {String} linePosition = [center | top] 横条位置
* @property {Number|String} lineWidth 横条宽度如果带%则为选中项宽度的百分比
* @property {Number|String} lineHeight 横条高度
* @property {Number|String} lineRadius 横条圆角
* @property {String} lineColor 横条颜色未指定则使用activeColor
* @property {String|Object} lineStyle 横条样式
* @property {Number|String} lineDuration 横条切换动画持续时间 ms
* @property {Boolean} border 底部是否显示下边框默认不显示
* @property {String} borderColor 下边框颜色
* @property {Boolean} showScrollBar 是否显示滚动条默认不显示
* @event {Function} click (index) 点击标签时触发
* @event {Function} change (index) 选中标签改变时触发
* @example <s-tabs v-model="activeTab" :list="list"/>
*/
export default {
name: 'STabs',
mixins: [componentMixin],
props: {
height: [Number, String],
list: {
type: Array,
default: () => [],
},
value: {
type: [Number, String, Boolean],
default: 0,
},
labelKey: {
type: String,
default: 'label',
},
valueKey: String,
background: String,
fontSize: [Number, String],
color: String,
activeColor: String,
activeBold: Boolean,
disabledColor: String,
ellipsis: Boolean,
tabStyle: [String, Object],
activeTabStyle: [String, Object],
showLine: {
type: Boolean,
default: true,
},
linePosition: {
type: String,
default: 'bottom',
},
lineWidth: {
type: [Number, String],
default: 50,
},
lineHeight: [Number, String],
lineColor: String,
lineRadius: [Number, String],
lineStyle: [String, Object],
lineDuration: {
type: [Number, String],
default: 400,
},
border: Boolean,
borderColor: String,
showScrollBar: Boolean,
},
data() {
return {
isInit: false,
scrollLeft: 0,
lineLeft: 0,
line_width: 0,
scrollWithAnimation: false,
};
},
computed: {
c_class() {
return this.$mergeClass({
's-hairline--bottom': this.border,
}, this.custom_class);
},
c_style() {
return this.$mergeStyle({
height: this.$addUnit(this.height),
background: this.background,
borderColor: this.borderColor,
}, this.custom_style);
},
c_list() {
return toValidListData(this.list, this.valueKey, this.labelKey);
},
current() {
if (this.valueKey) return this.c_list.findIndex(item => item[this.valueKey] === this.value);
const index = parseInt(this.value);
return index > -1 ? index : -1;
},
show_line() {
return this.isInit && this.showLine && this.current > -1;
},
line_style() {
return this.$mergeStyle({
width: this.$addUnit(this.line_width, 'px'),
height: this.$addUnit(this.lineHeight),
top: this.linePosition === 'top' ? 0 : this.linePosition === 'center' ? '50%' : '',
bottom: this.linePosition === 'bottom' ? 0 : '',
background: this.lineColor || this.activeColor,
borderRadius: this.$addUnit(this.lineRadius),
transform: `translate3d(${this.lineLeft}px,${this.linePosition === 'center' ? '-50%' : 0},0)`,
transitionDuration: `${this.lineDuration}ms`,
}, this.lineStyle);
},
tabClassList() {
return this.c_list.map((item, index) => {
return this.$mergeClass({
's-tab--active': this.isActive(index),
's-tab--disabled': item.disabled,
});
});
},
tabStyleList() {
return this.c_list.map((item, index) => {
const isActive = this.isActive(index);
return this.$mergeStyle({
fontSize: this.$addUnit(this.fontSize),
color: item.disabled ? this.disabledColor : isActive ? this.activeColor : this.color,
fontWeight: isActive && this.activeBold ? '500' : '',
overflow: this.ellipsis ? 'hidden' : '',
}, this.tabStyle, isActive ? this.activeTabStyle : '');
});
},
refreshState() {
return [
this.c_list,
this.value,
this.lineWidth,
];
},
},
watch: {
refreshState() {
this.refresh();
},
},
created() {
this.diffLeft = 0;
},
mounted() {
this.refresh();
},
methods: {
isActive(index) {
return this.current === index;
},
hasBadge(item) {
return this.$getType(item.badge) === 'object';
},
onTabClick(item, index) {
this.$emit('click', index);
if (!this.isActive(index) && !item.disabled) {
this.$emit('input', this.valueKey ? item[this.valueKey] : index);
this.$emit('change', index);
}
},
refresh() {
this.scrollWithAnimation = false;
this.$nextTick(() => {
if (!this._isMounted || this._isDestroyed || this.current < 0) return;
this.scrollWithAnimation = true;
Promise.all([
this.$getRect(`#${this.$sUid} .s-tabs__scroll,#${this.$sUid} .s-tabs__wrap`, true),
this.$getRect(`#${this.$sUid} .s-tab`, true),
]).then(([[scrollRect, wrapRect], listRect]) => {
const currentRect = listRect[this.current];
if (currentRect && this.scrollWithAnimation) {
const centerLeft = (scrollRect.width - currentRect.width) / 2;
if (this.scrollLeft === 0) this.diffLeft = wrapRect.left - scrollRect.left;
this.scrollLeft = Math.abs(wrapRect.left - scrollRect.left - this.diffLeft) + currentRect.left - centerLeft - scrollRect.left;
this.line_width = /%/.test(this.lineWidth) ? currentRect.width * parseFloat(this.lineWidth) / 100 : this.$toPx(this.lineWidth);
this.lineLeft = this.scrollLeft - this.diffLeft + centerLeft + (currentRect.width - this.line_width) / 2;
if (!this.isInit) {
this.$nextTick(() => {
this.isInit = true;
});
}
}
});
});
},
},
};
</script>
<style lang="scss" src="./index.scss"></style>