automated_uniapp/uni_modules/cool-ui/components/cl-tabs/cl-tabs.vue

368 lines
6.5 KiB
Vue
Raw Normal View History

2025-01-09 16:16:11 +08:00
<template>
<view
class="cl-tabs"
:class="[
{
'is-content': $slots.default,
'is-fill': fill,
'is-border': border,
'is-dropdown': showDropdown,
'is-checkable': checkable,
},
]"
:style="{
backgroundColor: backgroundColor,
}"
>
<view class="cl-tabs__header">
<scroll-view
class="cl-tabs__bar"
scroll-with-animation
scroll-x
:scroll-left="scrollLeft"
:style="{
height: parseRpx(height),
}"
>
<view
class="cl-tabs__bar-box"
:style="{
'justify-content': justify,
}"
>
<view
class="cl-tabs__bar-item"
v-for="(item, index) in tabs"
:key="index"
:style="{
color: current === item.value ? color : unColor,
padding: `0 ${gutter}rpx`,
}"
:class="{
'is-active': current === item.value,
}"
@tap="change(index)"
>
<!-- 前缀图标 -->
<text
v-if="item.prefixIcon"
class="cl-tabs__icon is-prefix"
:class="[item.prefixIcon]"
></text>
<!-- 文本内容 -->
<text class="cl-tabs__label">{{ item.label }}</text>
<!-- 后缀图标 -->
<text
v-if="item.suffixIcon"
class="cl-tabs__icon is-suffix"
:class="[item.suffixIcon]"
></text>
</view>
<!-- 选中样式 -->
<view
class="cl-tabs__line"
v-if="lineLeft > 0 && showLine"
:style="{
'background-color': color,
left: lineLeft + 'px',
}"
></view>
</view>
</scroll-view>
<!-- 下拉图标 -->
<view class="cl-tabs__dropdown" @tap="openDropdown" v-if="showDropdown">
<cl-icon :name="`${dropdown.visible ? 'arrow-top' : 'arrow-bottom'}`"></cl-icon>
</view>
<!-- 下拉列表 -->
<view
class="cl-tabs__dropdown-box"
:style="{
maxHeight: dropdown.visible ? dropdown.height : '0',
}"
>
<slot name="dropdown"></slot>
</view>
</view>
</view>
</template>
<script lang="ts">
import {
type PropType,
computed,
defineComponent,
getCurrentInstance,
nextTick,
onMounted,
reactive,
ref,
watch,
} from "vue";
import { parseRpx } from "/@/cool/utils";
export default defineComponent({
name: "cl-tabs",
props: {
// 绑定值
modelValue: [String, Number],
// 高度
height: {
type: [String, Number],
default: 80,
},
// 标签列表
list: {
type: Array as PropType<{ label: string; value: any; [key: string]: any }[]>,
default: [],
},
// 是否循环显示
loop: {
type: Boolean,
default: true,
},
// 标签是否填充
fill: Boolean,
// 水平布局
justify: {
type: String as PropType<"start" | "center" | "end">,
default: "start",
},
// 是否带有下边框
border: {
type: Boolean,
default: true,
},
// 标签间隔
gutter: {
type: Number,
default: 30,
},
// 选中颜色
color: {
type: String,
default: "",
},
// 未选中颜色
unColor: {
type: String,
default: "",
},
// 背景色
backgroundColor: {
type: String,
default: "#fff",
},
// 是否显示下拉按钮
showDropdown: Boolean,
// 显示下划线
showLine: {
type: Boolean,
default: true,
},
// 是否可选
checkable: Boolean,
// 是否禁用
disabled: Boolean,
},
emits: ["update:modelValue", "change"],
setup(props, { emit }) {
const { proxy }: any = getCurrentInstance();
// 当前选中
const current = ref<number | string>();
// 下划线左位移
const lineLeft = ref(0);
// 左滚动距离
const scrollLeft = ref(0);
// 左位移
const offsetLeft = ref(0);
// 宽度
const width = ref(375);
// 列表
const tabs = computed(() => props.list);
// 刷新
function refresh() {
nextTick(() => {
// 获取选项卡宽度
uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(proxy)
// #endif
.select(".cl-tabs")
.boundingClientRect((d: any) => {
offsetLeft.value = d.left;
width.value = d.width;
getRect();
})
.exec();
});
}
// 下拉
const dropdown = reactive({
visible: false,
height: "200rpx",
timer: null as any,
});
// 打开下拉框
function openDropdown() {
dropdown.visible = !dropdown.visible;
// 清除计时器
clearTimeout(dropdown.timer);
if (dropdown.visible) {
const fn = () => {
uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(proxy)
// #endif
.select(".cl-tabs__dropdown-box")
.boundingClientRect((res) => {
dropdown.height = res.height + "px";
})
.exec();
};
// 获取下拉区域高度
dropdown.timer = setTimeout(fn, 300);
}
}
// 关闭下拉框
function closeDropdown() {
dropdown.visible = false;
}
// 改变
async function change(index: number) {
if (props.disabled) {
return false;
}
let { value } = tabs.value[index];
if (props.checkable) {
if (value == current.value) {
value = undefined;
}
}
emit("update:modelValue", value);
emit("change", value);
current.value = value;
}
// 获取下标
function getIndex() {
return tabs.value.findIndex((e) => e.value == current.value);
}
// 上一个
function prev() {
let index = getIndex();
change(index <= 0 ? (props.loop ? tabs.value.length - 1 : 0) : index - 1);
}
// 下一个
function next() {
let index = getIndex();
change(
index >= tabs.value.length - 1
? props.loop
? 0
: tabs.value.length - 1
: index + 1,
);
}
const rect = ref<any>();
// 大小
function getRect() {
nextTick(() => {
uni.createSelectorQuery()
.in(proxy)
.selectAll(".cl-tabs__bar-item")
.fields({ rect: true, size: true }, () => {})
.exec((d) => {
rect.value = d[0];
onOffset();
});
});
}
// 间距
function onOffset() {
if (rect.value) {
nextTick(() => {
let item = rect.value[getIndex()];
if (item) {
let x = item.left - (width.value - item.width) / 2 - offsetLeft.value;
if (x < 0) {
x = 0;
}
scrollLeft.value = x;
lineLeft.value =
item.left + item.width / 2 - uni.upx2px(16) - offsetLeft.value;
}
});
}
}
// 监听绑定值
watch(
() => props.modelValue,
(val: any) => {
current.value = val;
onOffset();
},
{
immediate: true,
},
);
// 监听列表改变
watch(() => props.list, refresh);
onMounted(() => {
refresh();
});
return {
current,
scrollLeft,
lineLeft,
tabs,
dropdown,
change,
prev,
next,
openDropdown,
closeDropdown,
parseRpx,
};
},
});
</script>