【smart-app更新】1、版本更新记录;2、复杂表单‘3、引入tabs组件

This commit is contained in:
zhuoda 2024-03-13 21:05:28 +08:00
parent 3b31558adb
commit d170a9d189
18 changed files with 2717 additions and 368 deletions

View File

@ -14,4 +14,11 @@ export const changeLogApi = {
queryPage: (param) => { queryPage: (param) => {
return postRequest('/support/changeLog/queryPage', param); return postRequest('/support/changeLog/queryPage', param);
}, },
/**
* 详情 @author 卓大
*/
getDetail: (changeLogId) => {
return getRequest(`/support/changeLog/getDetail/${changeLogId}`);
},
}; };

View File

@ -2,7 +2,8 @@
"easycom": { "easycom": {
"autoscan": true, "autoscan": true,
"custom": { "custom": {
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue" "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
"^y-(.*)": "@/uni_modules/y-$1/components/y-$1.vue"
} }
}, },
"pages": [ "pages": [
@ -69,7 +70,7 @@
"path" : "pages/notice/notice-detail", "path" : "pages/notice/notice-detail",
"style" : "style" :
{ {
"navigationBarTitleText" : "通知公告", "navigationBarTitleText" : "公告内容",
"enablePullDownRefresh" : false, "enablePullDownRefresh" : false,
"navigationBarBackgroundColor": "#fff" "navigationBarBackgroundColor": "#fff"
} }
@ -92,6 +93,15 @@
"navigationBarBackgroundColor": "#fff" "navigationBarBackgroundColor": "#fff"
} }
}, },
{
"path" : "pages/support/change-log/change-log-detail",
"style" :
{
"navigationBarTitleText" : "版本更新内容",
"enablePullDownRefresh" : false,
"navigationBarBackgroundColor": "#fff"
}
},
{ {
"path" : "pages/list/list", "path" : "pages/list/list",
"style" : "style" :

View File

@ -1,49 +0,0 @@
<template>
<view class="font-item-box">
<text :class="modelValue==0?'active':''" @click="modelValue=0">标准</text>
<view></view>
<text :class="modelValue==1?'active':''" @click="modelValue=1">大号</text>
<view></view>
<text :class="modelValue==2?'active':''" @click="modelValue=2">小号</text>
</view>
</template>
<script setup>
import { watch } from 'vue';
const emits = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue:{
type:Number,
default:0
}
})
watch(()=>props.modelValue,(newValue,oldValue)=>{
emits('update:modelValue',newValue)
})
</script>
<style lang="scss" scoped>
.font-item-box {
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
text {
font-size: 30rpx;
color: #cccccc;
&.active {
color: #1A9AFF;
}
}
view {
width: 4rpx;
height: 16rpx;
background-color: #e6e6e6;
margin: 0 42rpx;
}
}
</style>

View File

@ -1,59 +0,0 @@
<template>
<view class="card-content">
<view @click="modelValue = index" :class="modelValue == index?'active':''"
v-for="(item,index) in list" :key="index">
{{item}}
</view>
</view>
</template>
<script setup>
import { watch } from 'vue';
const emits = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue:{
type:Number,
default:0
},
list:{
type:Array,
default:[]
}
})
watch(()=>props.modelValue,(newValue,oldValue)=>{
emits('update:modelValue',newValue)
})
</script>
<style lang="scss" scoped>
.card-content {
padding: 0 30rpx 24rpx;
display: flex;
flex-wrap: wrap;
view {
box-sizing: border-box;
width: 197rpx;
height: 72rpx;
background: #f7f8f9;
border-radius: 8rpx;
text-align: center;
line-height: 72rpx;
margin-right: 24rpx;
margin-top: 24rpx;
font-size: 30rpx;
color: #323333;
border: 2rpx solid #f7f8f9;
&:nth-child(3n) {
margin-right: 0;
}
}
.active {
background: #eff8ff;
border: 2rpx solid #2291f9;
}
}
</style>

View File

@ -1,60 +0,0 @@
<template>
<view class="sex-box">
<view class="sex-item" :class="modelValue?'active':''" @click="modelValue=true">
<uni-icons type="circle" v-if="!modelValue" color="#ccc" size="30"></uni-icons>
<uni-icons v-else type="checkbox-filled" color="#1A9AFF" size="30"></uni-icons>
<view>
</view>
</view>
<view class="sex-item" :class="!modelValue?'active':''" @click="modelValue=false">
<uni-icons type="circle" v-if="modelValue" color="#ccc" size="30"></uni-icons>
<uni-icons v-else type="checkbox-filled" color="#1A9AFF" size="30"></uni-icons>
<view>
</view>
</view>
</view>
</template>
<script setup>
import { watch } from 'vue';
const emits = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue:{
type:Boolean,
default:true
}
})
watch(()=>props.modelValue,(newValue,oldValue)=>{
emits('update:modelValue',newValue)
})
</script>
<style lang="scss" scoped>
.sex-box {
width: 100%;
display: flex;
justify-content: flex-end;
.sex-item {
display: flex;
width: 112rpx;
height: 64rpx;
background-color: #f3f3f3;
border: 1rpx solid #ededed;
align-items: center;
margin-left: 40rpx;
justify-content: center;
border-radius: 8rpx;
font-size: 32rpx;
}
.active {
background: #f1f9ff;
border: 1rpx solid #1a9aff;
color: #1A9AFF;
}
}
</style>

View File

@ -1,199 +1,94 @@
<template> <template>
<view class=""> <view class="smart-form">
<view class="form-card"> <uni-forms :label-width="100" :modelValue="formData" label-position="left">
<view class="title"> 常用功能 </view> <view class="smart-form-group">
<view class="content"> <view class="smart-form-group-title"> 常用功能 </view>
<uni-forms :label-width="100" :modelValue="formData" label-position="left"> <view class="smart-form-group-content">
<uni-forms-item class="uni-forms-item" label="姓名" name="name"> <uni-forms-item class="smart-form-item" label="姓名" name="name" required>
<input class="input" type="text" v-model="formData.name" placeholder="请输入姓名" /> <uni-easyinput trim="all" v-model="formData.name" placeholder="请输入姓名" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item class="uni-forms-item" label="手机号码" name="name"> <uni-forms-item class="smart-form-item" label="手机号码" name="name" required>
<input class="input" type="text" v-model="formData.name" placeholder="请输入手机号码" /> <uni-easyinput trim="all" v-model="formData.name" placeholder="请输入手机号码" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item class="uni-forms-item" label="邮箱地址" name="name"> <uni-forms-item class="smart-form-item" label="邮箱地址" name="name">
<input class="input" type="text" v-model="formData.name" placeholder="请输入邮箱地址" /> <uni-easyinput trim="all" v-model="formData.name" placeholder="请输入邮箱地址" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item class="uni-forms-item" label="性别" name="name"> <uni-forms-item class="smart-form-item" label="性别" required>
<RadioSex v-model="formData.sex"></RadioSex> <uni-data-checkbox v-model="formData.sex" :localdata="sexs" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item class="uni-forms-item" label="出生日期" name="name"> <uni-forms-item class="smart-form-item" label="出生日期" name="name">
<view class="item-box"> <uni-datetime-picker type="date" return-type="timestamp" v-model="formData.datetimesingle" />
<picker ref="datePickerRef" mode="date" @change="bindDateChange">
<input ref="dateInputRef" class="input" type="text" v-model="date" placeholder="点击选择时间" />
</picker>
</view>
</uni-forms-item> </uni-forms-item>
<uni-forms-item class="uni-forms-item" label="所在地" name="name"> </view>
<input class="input" disabled type="text" v-model="formData.name" placeholder="点击选择所在地" />
</uni-forms-item>
</uni-forms>
</view> </view>
</view> <view class="smart-form-group">
<view class="form-card"> <view class="smart-form-group-title"> 兴趣爱好 </view>
<view class="title"> 推送用户 </view> <view class="smart-form-group-content">
<view class="content"> <uni-forms-item class="smart-form-item" label="兴趣爱好" name="interest">
<uni-forms :label-width="100" :modelValue="formData" label-position="left"> <uni-data-checkbox mode="button" multiple v-model="formData.interest" :localdata="interestList"></uni-data-checkbox>
<uni-forms-item class="uni-forms-item" label="选择用户" name="name">
<view class="item-box" @click="openSelectPeople">
<image class="user-select-image" src="/src/static/images/form/add.png" mode=""></image>
</view>
</uni-forms-item> </uni-forms-item>
</uni-forms> </view>
</view> </view>
</view> <view class="smart-form-group">
<view class="form-card"> <view class="smart-form-group-title"> 屏幕设置 </view>
<view class="title"> 兴趣爱好 </view> <view class="smart-form-group-content">
<Interest v-model="formData.interest" :list="interestList"></Interest> <uni-forms-item class="smart-form-item" label="亮度调整" name="name">
</view> <slider style="width: 100%" value="50" activeColor="#2291F9" backgroundColor="#f5f6f8" block-color="#2291F9" block-size="20" />
<view class="form-card">
<view class="title"> 推送用户 </view>
<view class="content">
<uni-forms :label-width="100" :modelValue="formData" label-position="left">
<uni-forms-item class="uni-forms-item" label="亮度调整" name="name">
<view class="item-box">
<slider style="width: 100%" value="50" activeColor="#2291F9" backgroundColor="#f5f6f8" block-color="#2291F9" block-size="20" />
</view>
</uni-forms-item> </uni-forms-item>
<uni-forms-item class="uni-forms-item" label="字体大小" name="name"> </view>
<FontSizeSelece v-model="formData.fontType"></FontSizeSelece> </view>
<view class="smart-form-group">
<view class="smart-form-group-title"> 自我介绍 </view>
<view class="smart-form-group-content">
<uni-forms-item class="smart-form-item" label="兴趣爱好" name="interest">
<uni-easyinput type="textarea" autoHeight v-model="value" placeholder="请输入自我介绍" />
</uni-forms-item> </uni-forms-item>
</uni-forms>
</view> <uni-forms-item class="smart-form-item" label="上传图片" name="interest">
</view> <uni-file-picker limit="9" title="最多选择9张图片" />
<view class="form-card"> </uni-forms-item>
<view class="title"> 自我介绍 </view> </view>
<view class="content">
<uni-forms :modelValue="formData" label-position="left">
<view class="textarea">
<textarea auto-height style="font-size: 30rpx" placeholder="请输入自我介绍" placeholder-class="textarea-placeholder" />
</view>
<view class="example-body">
<uni-file-picker limit="9" title="上传图片">
<image style="width: 100%; height: 100%" src="/static/images/form/add-image.png" mode=""></image>
</uni-file-picker>
</view>
</uni-forms>
</view> </view>
</uni-forms>
<view class="smart-form-submit fixed-bottom-button">
<button class="smart-form-submit-btn smart-margin-right20" type="default">取消</button>
<button class="smart-form-submit-btn" type="warn">重置</button>
<button class="smart-form-submit-btn" type="primary">确定</button>
</view> </view>
</view> </view>
</template> </template>
<script setup> <script setup>
import RadioSex from './components/radio-sex.vue'; import { reactive } from 'vue';
import Interest from './components/interest.vue';
import FontSizeSelece from './components/font-size-select.vue';
import { reactive, ref } from 'vue';
const interestList = ['唱歌', '跳舞', 'RAP', '篮球', '音乐', '唱歌', '跳舞', 'RAP', '篮球']; const sexs = [
const formData = reactive({ {
interest: 4, text: '男',
fontType: 0, value: 0,
}); },
const hobby = ref(''); {
const date = ref(); text: '女',
const bindDateChange = (e) => { value: 1,
date.value = e.detail.value; },
}; {
text: '你懂的',
value: 2,
},
];
const openSelectPeople = () => { const interestList = [
uni.navigateTo({ { text: '唱歌', value: 1 },
url: '/pages/select-people/select-people', { text: '足球', value: 2 },
}); { text: '篮球', value: 3 },
}; { text: '跑步', value: 4 },
{ text: '写字', value: 5 },
{ text: '美术', value: 6 },
{ text: '射击', value: 7 },
{ text: '健身', value: 8 },
{ text: '马术', value: 9 },
{ text: '美食', value: 10 },
];
const formData = reactive({});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
page {
background: #f5f6f8;
}
::v-deep .uni-forms-item__content {
display: flex;
align-items: center;
}
::v-deep .uni-forms-item__label {
font-size: 32rpx;
color: #000000;
padding-top: 28rpx;
}
::v-deep .uni-forms-item {
margin-bottom: 0 !important;
}
::v-deep .uni-slider-thumb {
background: #fff !important;
border: 10rpx solid #1a9aff;
box-sizing: border-box;
}
.uni-forms-item {
height: 100rpx;
border-bottom: 1rpx solid #ededed;
&:last-child {
border: none;
}
}
.form-card {
box-sizing: border-box;
width: 700rpx;
margin: 20rpx auto 0;
background: #fff;
border-radius: 16rpx;
.title {
width: 100%;
height: 84rpx;
background-image: url('/static/images/list/form-list.png');
background-size: 100% 84rpx;
line-height: 84rpx;
text-indent: 30rpx;
font-size: 32rpx;
color: #323333;
font-weight: bold;
}
.content {
padding: 0 30rpx;
}
.input {
font-size: 30rpx;
text-align: right;
width: 100%;
}
}
.item-box {
width: 100%;
display: flex;
justify-content: flex-end;
}
.user-select-image {
width: 40rpx;
height: 40rpx;
}
.textarea {
background: #fcfcfc;
border: 0.5px solid #ededed;
border-radius: 4px;
width: 100%;
height: 320rpx;
margin-top: 24rpx;
padding: 24rpx 30rpx;
box-sizing: border-box;
.textarea-placeholder {
color: #cccccc;
font-size: 30rpx;
}
}
.example-body {
padding-bottom: 24rpx;
}
</style>

View File

@ -38,10 +38,15 @@
<uni-grid-item class="menu-grid"> <uni-grid-item class="menu-grid">
<view class="menu-item" @click="navigateTo('/pages/form/form')"> <view class="menu-item" @click="navigateTo('/pages/form/form')">
<image class="item-image" src="/@/static/images/index/ic_home_menu6.png"></image> <image class="item-image" src="/@/static/images/index/ic_home_menu6.png"></image>
<view class="item-text"> 表单 </view> <view class="item-text"> 复杂表单 </view>
</view>
</uni-grid-item>
<uni-grid-item class="menu-grid">
<view class="menu-item" @click="switchTab('/pages/list/list')">
<image class="item-image" src="/@/static/images/index/ic_home_menu9.png"></image>
<view class="item-text"> 常见列表 </view>
</view> </view>
</uni-grid-item> </uni-grid-item>
<uni-grid-item class="menu-grid"> <uni-grid-item class="menu-grid">
<view class="menu-item" @click="navigateTo('/pages/order-detail/order-detail')"> <view class="menu-item" @click="navigateTo('/pages/order-detail/order-detail')">
<image class="item-image" src="/@/static/images/index/ic_home_menu7.png"></image> <image class="item-image" src="/@/static/images/index/ic_home_menu7.png"></image>
@ -54,12 +59,6 @@
<view class="item-text"> 优惠券 </view> <view class="item-text"> 优惠券 </view>
</view> </view>
</uni-grid-item> </uni-grid-item>
<uni-grid-item class="menu-grid">
<view class="menu-item" @click="navigateTo('/pages/list/list')">
<image class="item-image" src="/@/static/images/index/ic_home_menu9.png"></image>
<view class="item-text"> 精品课程 </view>
</view>
</uni-grid-item>
<uni-grid-item class="menu-grid"> <uni-grid-item class="menu-grid">
<view class="menu-item" @click="navigateTo('/pages/change-log/change-log-list')"> <view class="menu-item" @click="navigateTo('/pages/change-log/change-log-list')">
<image class="item-image" src="/@/static/images/index/ic_home_menu10.png"></image> <image class="item-image" src="/@/static/images/index/ic_home_menu10.png"></image>
@ -82,6 +81,11 @@
url, url,
}); });
} }
function switchTab(url) {
uni.switchTab({
url,
});
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -66,5 +66,5 @@ const tabsList = [{
color: #ccc; color: #ccc;
} }
} }
</style> </style>

View File

@ -0,0 +1,79 @@
<template>
<view class="container">
<view class="title">
<uni-title type="h1" align="center" :title="detail.title"></uni-title>
<uni-title type="h4" align="center" color="#999999" :title="detail.subTitle"></uni-title>
</view>
<view class="content">
<rich-text :nodes="detail.content" />
</view>
</view>
</template>
<script setup>
import { inject, reactive } from 'vue';
import { changeLogApi } from '/@/api/support/change-log-api';
import { onLoad } from '@dcloudio/uni-app';
import { smartSentry } from '/@/lib/smart-sentry';
const smartEnumPlugin = inject('smartEnumPlugin');
const detail = reactive({
title: '',
subTitle: '',
content: '',
});
async function getDetail(changeLogId) {
try {
uni.showLoading({ title: '加载中' });
let res = await changeLogApi.getDetail(changeLogId);
detail.title = res.data.version + '版本' + smartEnumPlugin.getDescByValue('CHANGE_LOG_TYPE_ENUM', res.data.type);
detail.content =
'<pre style="' +
'line-height: 18px;\n' +
'font-size: 14px;\n' +
'white-space: pre-wrap;\n' +
' white-space: -moz-pre-wrap;\n' +
' white-space: -pre-wrap;\n' +
' white-space: -o-pre-wrap;\n' +
' word-wrap: break-word;">' +
res.data.content +
'</pre>';
let subTitleArray = [];
if (res.data.publishAuthor) {
subTitleArray.push(res.data.publishAuthor);
}
if (res.data.publicDate) {
subTitleArray.push(res.data.publicDate);
}
detail.subTitle = subTitleArray.join(' | ');
} catch (e) {
smartSentry.captureError(e);
} finally {
uni.hideLoading();
}
}
onLoad((option) => {
uni.pageScrollTo({
scrollTop: 0,
});
getDetail(option.changeLogId);
});
</script>
<style lang="scss" scoped>
.container {
height: 100vh;
padding: 20rpx;
.content {
border-top: #cccccc 1px solid;
margin-top: 50rpx;
padding: 50rpx 16rpx 50rpx 16rpx;
line-height: 30px;
color: #333333;
}
}
</style>

View File

@ -25,23 +25,20 @@
<view class="list-container"> <view class="list-container">
<view class="list-item" @click="gotoDetail(item.changeLogId)" v-for="item in listData" :key="item.changeLogId"> <view class="list-item" @click="gotoDetail(item.changeLogId)" v-for="item in listData" :key="item.changeLogId">
<view class="list-item-row"> <view class="list-item-row">
<view class="list-item-content bolder">{{ item.version }}</view> <view class="list-item-content bolder"
>{{ item.version }}版本{{ $smartEnumPlugin.getDescByValue('CHANGE_LOG_TYPE_ENUM', item.type) }}</view
>
<uni-tag <uni-tag
:text="$smartEnumPlugin.getDescByValue('CHANGE_LOG_TYPE_ENUM', item.type)" :text="$smartEnumPlugin.getDescByValue('CHANGE_LOG_TYPE_ENUM', item.type)"
:type="$smartEnumPlugin.getObjectByValue('CHANGE_LOG_TYPE_ENUM', item.type).type" :type="$smartEnumPlugin.getObjectByValue('CHANGE_LOG_TYPE_ENUM', item.type).type"
/> />
</view> </view>
<view class="list-item-row"> <view class="list-item-row">
<view class="list-item-label">发布日期{{ item.publicDate }} - {{ item.publishAuthor }}</view> <view class="list-item-label">发布日期{{ item.publicDate }}</view>
</view>
<view class="list-item-row">
<view class="list-item-label">{{ item.content }}</view>
</view> </view>
</view> </view>
</view> </view>
</mescroll-body> </mescroll-body>
<uni-fab ref="fab" :pattern="fabPattern" horizontal="right" @fabClick="gotoAdd" />
</view> </view>
</template> </template>
@ -121,7 +118,7 @@
// --------------------------- --------------------------------- // --------------------------- ---------------------------------
function gotoDetail(id) { function gotoDetail(id) {
uni.navigateTo({ url: '/pages/enterprise/enterprise-detail?enterpriseId=' + id }); uni.navigateTo({ url: '/pages/support/change-log/change-log-detail?changeLogId=' + id });
} }
</script> </script>
@ -172,9 +169,10 @@
font-size: 30rpx; font-size: 30rpx;
font-weight: 400; font-weight: 400;
text-align: left; text-align: left;
color: $uni-text-color-grey;
} }
.bolder { .bolder {
font-weight: 600 !important; font-weight: 500 !important;
font-size: 34rpx !important; font-size: 34rpx !important;
} }
.list-item-content { .list-item-content {

View File

@ -136,9 +136,9 @@
} }
.smart-form-item { .smart-form-item {
height: 100rpx; min-height: 100rpx;
height: auto;
padding-bottom: 24rpx; padding-bottom: 24rpx;
//border-bottom: 1rpx solid #ededed;
align-items: center; align-items: center;
&:last-child { &:last-child {
border: none; border: none;
@ -191,4 +191,9 @@
flex: 1; flex: 1;
} }
} }
.fixed-bottom-button {
position: fixed;
bottom: 0;
}
} }

View File

@ -0,0 +1,464 @@
.y-tabs {
position: relative;
display: block;
// 标签栏垂直方位下的根容器样式
&.is-vertical {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
justify-content: flex-end;
flex: 1;
// 垂直时标签栏scroll-view的子项垂直排列
.y-tabs__scroll {
flex-direction: column;
}
}
// 区域滚动下的滚动导航
&.is-areaScroll.is-scrollNav {
display: flex;
height: 100vh;
flex-direction: column;
// 标签栏不收缩
.y-tabs__wrap {
flex-shrink: 0;
}
.y-tabs__track,
.y-tabs__content-scrollview {
height: 100%;
}
}
// 区域滚动下的侧边栏导航
&.is-areaScroll.is-sidebarNav {
display: flex;
height: 100vh;
flex-direction: row;
.y-tabs__scroll {
height: 100%;
}
// 标签栏不收缩
.y-tabs__wrap {
flex-shrink: 0;
}
.y-tabs__track,
.y-tabs__content-scrollview {
height: 100%;
}
}
}
// 依赖元素
.y-tabs__depend {
position: absolute;
top: 0;
left: 0;
height: 1px; //必须保证有高度否则observer无效
width: 100%;
}
// 透明标签栏所需的依赖元素
.y-tabs__depend--transparent {
position: absolute;
top: 0;
left: 0;
height: 1px; //必须保证有高度否则observer无效
width: 1px;
}
// 模拟标签栏吸顶时设置offset时距屏幕顶部的元素
.y-tabs__depend--offset {
position: fixed;
top: 0;
left: 0;
z-index: -1;
height: 1px;
}
// 标签栏占位元素
.y-tabs__placeholder {
position: relative;
}
// 标签垂直展示且吸顶时标签栏占位元素不伸缩
.y-tabs.is-fixed.is-vertical .y-tabs__placeholder {
flex-shrink: 0;
}
// 文字省略
.y-tabs__ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// 导航区域包裹层
.y-tabs__wrap {
position: relative;
display: flex;
align-items: center;
overflow: hidden;
visibility: visible;
background: #fff;
touch-action: none;
// 标签栏垂直展示时包裹层样式
&.is-vertical {
width: 100px;
display: flex;
flex-direction: column;
flex-shrink: 0;
}
// 粘性定位布局下的导航区域包裹层
&.is-fixed {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 99;
}
// 标签垂直展示且吸顶时给定bottom否则scroll-view不会滚动
&.is-fixed.is-vertical {
bottom: 0;
}
// 透明的导航区域包裹层
&.is-transparent {
background: rgba(255, 255, 255, 0);
}
// 标签栏水平时按钮风格的包裹层
&.is-button:not(.is-vertical),
&.is-line-button:not(.is-vertical) {
padding: 0 8px;
}
}
// scroll-view组件样式
.y-tabs__scroll {
position: relative;
width: 100%;
white-space: nowrap; // 使用横向滚动时需要给<scroll-view>添加white-space: nowrap;样式
flex-direction: row;
}
// 条件编译不放在样式中vue3无效
// H5APP端去滚动条
// 小程序端会报Some selectors are not allowed in component wxss, including tag name selectors, ID selectors, and attribute selectors.
/* #ifdef H5 || APP */
.y-tabs__scroll ::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
-webkit-appearance: none;
background: transparent;
color: transparent;
}
/* #endif */
// IOS 13 以下的系统当滚动区域设置了-webkit-overflow-scrolling: touch;必须设置否者几乎无法滚动::-webkit-scrollbar 相关属性会失效iOS 13 已经修复了此Bug
// 小程序端: 去除 scroll-view 组件的滚动条
/* #ifndef H5 || APP */
::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
-webkit-appearance: none;
background: transparent;
color: transparent;
}
/* #endif */
// 导航区域
.y-tabs__nav {
position: relative;
box-sizing: border-box;
user-select: none;
flex: 1;
display: flex;
&.is-shrink{
display: inline-flex;
}
// 卡片风格
&.is-card {
margin: 6px 16px;
border-radius: 4px;
box-sizing: border-box;
border: 1px solid #0022ab;
}
// 标签栏垂直时导航区域样式
&.is-vertical {
flex-direction: column;
height: auto;
.y-tab {
flex: unset;
}
}
// 标签左侧右侧的补充区域
&-left,
&-right {
position: relative;
display: inline-flex;
white-space: nowrap;
}
}
// 导航标签
.y-tab {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
height: 40px;
font-size: 28rpx;
color: #646566;
text-align: center;
padding: 0 4px;
flex: 1;
cursor: pointer;
// webkit的css扩展1-webkit-tap-highlight-color:这个属性是用于设定元素在移动设备如AdnroidiOS上被触发点击事件时响应的背景框的颜色有事件监听的元素被点击的时候会被高亮显示比如我的android上表现为一个蓝框加上半透明的背景
-webkit-tap-highlight-color: transparent;
// transition-duration: 0.2s;
// transition-property: background;
flex-shrink: 0;
z-index: 2;
// 选中状态
&.is-active {
color: #323233;
font-weight: 500;
}
// 禁用状态
&.is-disabled {
color: #c8c9cc !important;
cursor: not-allowed;
}
// 收缩布局
&.is-shrink {
flex: none;
padding: 0 8px;
}
//卡片风格
&.is-card {
height: 28px;
line-height: 28px;
}
}
// 标题区域
.y-tab__title {
overflow: hidden;
display: flex;
align-items: center;
height: inherit;
}
// 标题区域垂直排列
.y-tab__title--top,
.y-tab__title--bottom {
flex-direction: column;
}
// 标题文字
.y-tab__text {
position: relative;
display: block;
line-height: 1.2;
order: 2;
white-space: nowrap; //字节会设置white-space:normal
}
// 标签垂直展示时未达到文字超出隐藏的条件时
.y-tabs__nav.is-vertical .y-tab__text:not(.y-tabs__ellipsis) {
white-space: normal;
}
// 使用order排序
.y-tab__text--left,
.y-tab__text--top {
order: 0;
}
// 标题图标/图片包裹层
.y-tab__icons {
display: flex;
align-items: center;
order: 1;
z-index: 1;
}
//标题图片
.y-tab__image {
width: 20px;
height: 20px;
}
// 右上角信息区域
.y-tab__info {
display: inline-flex;
position: relative;
&--dot,
&--badge {
display: inline-block;
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
background-color: #e53935;
transform-origin: 100%;
}
// 小红点
&--dot {
width: 6px;
height: 6px;
border-radius: 100%;
transform: translate(0%, -180%);
}
// 徽标
&--badge {
line-height: 13px;
min-width: 18px;
border-radius: 18px;
padding: 0 2px;
transform: translate(0%, -120%);
font-size: 18rpx;
font-weight: 500;
text-align: center;
color: #fff;
}
}
// 底部条滑块
.y-tabs__bar {
position: absolute;
display: inline-flex;
left: 0;
z-index: 1;
width: 20px;
height: 3px;
border-radius: 3px;
background-color: #0022ab;
// line风格的滑块
&.is-line {
z-index: 2; //z-index与y-tab一样,避免被遮挡
// 标签水平展示时
&:not(.is-vertical) {
bottom: 3px;
width: 20px;
height: 3px;
border-radius: 3px;
}
// 标签垂直展示时
&.is-vertical {
top: 0;
left: 3px;
width: 3px;
height: 20px;
border-radius: 3px;
}
}
// buttonline-button风格的滑块
&.is-button,
&.is-line-button {
// top: 0;
justify-content: center;
align-items: center;
border-radius: 26px;
// 标签水平展示时
&:not(.is-vertical) {
height: calc(100% - 8px);
bottom: 4px;
}
// 标签垂直展示时
&.is-vertical {
width: calc(100% - 8px);
height: calc(100% - 8px);
}
}
// 线性按钮风格的滑块
&.is-line-button {
background-color: transparent;
border: 2rpx solid transparent;
}
}
// 标签内容
.y-tabs__content {
display: block;
position: relative;
overflow: hidden; //会导致uni-data-select无法撑开显示下拉选项,最好给pane中的内容设置一个高度(如果包裹select的父元素都没有设置relative则不会裁剪absolute属性的元素)
// 标签栏垂直展示内容减去标签栏默认宽度
&.is-vertical {
width: 100%;
}
}
// 标签内容的滑动轨道容器
.y-tabs__track {
position: relative;
display: flex;
width: 100%;
will-change: left;
// 滚动导航模式下内容卡片垂直排列
&.is-scrollspy {
flex-direction: column;
}
}
// 标签内容卡片
.y-tab__pane {
flex-shrink: 0;
box-sizing: border-box;
width: 100%;
height: 0;
position: relative;
flex-direction: row;
display: block;
visibility: visible;
// 选中时
&.is-active {
height: auto;
}
// 滚动导航
&.is-scrollspy {
height: auto;
}
}
.y-tab__pane--wrap {
position: relative;
}
// 区域滚动下的标签内容scroll-view
.y-tabs__content-scrollview {
flex-direction: column;
}

View File

@ -0,0 +1,194 @@
// styleIsolation组件样式隔离方式具体配置选项参见微信小程序自定义组件的样式
// 自定义组件 JSON 中的 styleIsolation 选项从基础库版本 2.10.1 开始支持。它支持以下取值:
// isolated 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(一般情况下的默认值);
// apply-shared 表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面;
// shared 表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了 apply-shared 或 shared 的自定义组件。(这个选项在插件中不可用。)
const options = {
styleIsolation: 'shared',
virtualHost: true // [微信小程序、支付宝小程序(默认值为 true] 将自定义节点设置成虚拟的更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等,而是希望自定义组件内部的第一层节点能够响应 flex 布局或者样式由自定义组件本身完全决定
// 微信可以使用virtualHost配置/QQ/百度/字节跳动这四家小程序自定义组件在渲染时会比App/H5端多一级节点导致flex无效是否考虑在组件上增加class控制
}
const emits = [
"input",
'update:modelValue', // 更新v-model绑定的变量
'click', //点击标签时触发 回调参数name标识符title标题
'change', //当前激活的标签改变时触发 回调参数name标识符title标题
'disabled', //点击被禁用的标签时触发 回调参数name标识符title标题
'rendered', //标签内容首次渲染时触发(仅在开启延迟渲染后触发) 回调参数name标识符title标题
'sticky-change', //吸顶时触发,仅在 sticky 模式下生效 回调参数name标识符title标题
'loaded', //组件内部初始化完成后调用 回调参数:{ isFixed: 是否吸顶 }
'slide-change', //内容页滑动时触发仅barAnimateMode为linear、worm、worm-ease时有效 回调参数:{ dx滑动距离 rate当前滑动长度占滑动区域的比例targetIndex目标下标}
'slide-end' //内容页滑动结束时触发仅barAnimateMode为linear、worm、worm-ease时有效 回调参数:{ targetIndex目标下标}
];
const props = {
// v-model绑定属性绑定当前选中标签的标识符标签的下标
value: {
type: [Number, String],
default: 0,
},
modelValue: {
type: [Number, String],
default: 0,
},
// 样式风格类型,可选值为 text、card、button、line-button
type: {
type: String,
default: "line",
validator(value) {
return ['line', 'text', 'card', 'button', 'line-button'].includes(value)
}
},
color: {
type: [String, null],
default: "#0022AB"
}, //标签主题色, 默认值为"#0022AB"
background: {
type: [String, null],
// default: "#fff"
}, //标签栏背景色,默认值为"#fff"
// 标签栏样式
wrapStyle: {
type: [Object, null],
default: () => {}
},
// 标签栏的展示方位,可选值vertical。
direction: {
type: String,
default: "horizontal",
validator(value) {
return ['horizontal', 'vertical'].includes(value)
}
},
titleActiveColor: String, //标题选中态颜色
titleInactiveColor: String, //标题默认态颜色
// 是否开启左侧收缩布局,开启后,所有的标签会向左侧收缩对齐。
shrink: {
type: Boolean,
default: false
},
// 动画时间单位秒默认为0.3s。仅支持type为line、button、line-button的滑块切换动画切换标签内容时的转场动画、滚动导航下的内容定位动画。
duration: {
type: [Number, String],
default: 0.2,
},
// 滑块宽度默认单位为px, 支持数字、rpx、vh、vw等单位及calc() 函数。 仅支持type为line、button、line-button。
// 标签栏水平/垂直展示时,type为line,宽度默认为20px/3px, 而type为button、line-button时,宽度默认为选中标签宽度-8px。
barWidth: [Number, String], //inherit继承tab的宽高
// 滑块高度默认单位为px, 支持数字、rpx、vh、vw等单位及calc() 函数。 仅支持type为line、button、line-button。
// 标签栏水平/垂直展示时,type为line,高度默认为3px/20px, 而type为button、line-button时,宽度默认为选中标签高度-8px。
barHeight: [Number, String],
//滑块样式仅支持type为line、button、line-button。
barStyle: Object,
// 滑动切换tab内容时滑块的动画模式默认值为line即切换tab时滑块宽度保持不变线性运动。可选值为worm(毛毛虫效果)、worm-ease(毛毛虫缓动)、none(不设置)。
// 可结合swiper组件使用滑动效果更好。
// 仅支持type为line。
barAnimateMode: {
type: String,
default: "linear",
validator(value) {
return ['none', 'linear', 'worm', 'worm-ease'].includes(value);
}
},
// 标签宽高是否动态变化
// 表示标签切换了选中状态后宽高是否有变化,有则需要开启该属性,否则会导致滑块错位
isDynamic: {
type: Boolean,
default: false,
},
// 是否省略过长的标题文字。标签栏水平展示时,如果标签数量未超过滚动阈值则生效,垂直展示不限制。
ellipsis: {
type: Boolean,
default: true,
},
// 滚动阈值,标签数量超过阈值且总宽度超过标签栏宽度时开始横向滚动
scrollThreshold: {
type: [Number, String],
default: 5
},
// 标签栏滚动时当前标签居中
scrollToCenter: {
type: Boolean,
default: true,
},
// 切换标签前的回调函数,返回 false 可阻止切换,支持返回 Promise
beforeChange: Function,
// 是否开启延迟渲染(首次切换到标签时才触发内容渲染)
isLazyRender: Boolean,
// 是否开启切换动画
// 用于标签栏滚动动画、切换标签内容时的转场动画、滚动导航下的内容定位动画
animated: {
type: Boolean,
default: true
},
// 在滚动导航模式下,滚动到最后一个标签内容但其顶部未超过可视区域时,是否激活对应的标签项
activeLast: {
type: Boolean,
default: false,
},
// ---------------------------------- 用于内容区域左右滑动的配置 ----------------------------------------
// 是否开启手势滑动切换
swipeable: {
type: Boolean,
default: false,
},
// 是否开启标签内容滑动时的拖动动画
// swipeable为true时有效建议设置is-lazy-render=false。该属性开启时考虑给包裹内容的容器增加一个min-height因为其他未显示的标签内容会沿用当前显示的高度拖动切换后由于高度不一致会有回弹
swipeAnimated: {
type: Boolean,
default: true,
},
// 滑动切换的滑动距离阈值单位为px表示开启手势滑动时横向滑动多少px切换标签内容快速滑动时不受限制
swipeThreshold: {
type: [Number, String],
default: 120,
},
// ---------------------------------- 用于滚动吸顶的配置 ----------------------------------------
// 是否使用粘性定位布局进行滚动吸顶
sticky: Boolean,
// 粘性布局下与顶部的最小距离单位为px
offsetTop: {
type: Number,
default: 0
},
// 粘性布局下标签栏的z-index值
zIndex: {
type: Number,
default: 99
},
// 粘性布局的判断阈值:表示在页面滚动过程中,标签栏距屏幕顶部多少px时触发吸顶函数进行吸顶判断
stickyThreshold: {
type: Number,
default: 0
},
// 页面滚动过程中,标题栏背景色是否透明渐变
// background属性值必须为rgba格式
transparent: {
type: Boolean,
default: false
},
// 标题栏背景色透明渐变的滚动距离
transparentOffset: {
type: Number,
default: 100
},
// 是否开启滚动导航;该模式下,内容将会平铺展示
// 如果标签栏垂直展示,且内容平铺展示,就为侧边栏模式
scrollspy: Boolean,
// 滚动导航模式下,内容区域是否跟随页面滚动
// 为true时整体区域跟随页面而滚动为false时内容区域是放在scroll-view中实现的局部滚动
pageScroll: {
type: Boolean,
default: true
},
}
export {
options,
emits,
props,
}

View File

@ -0,0 +1,75 @@
import { getDirection, now } from "./uitls"
export const touchMixin = {
data() {
return {
direction: '', //滑动方向
startX: '', //开始滑动的x坐标
startY: '', //开始滑动的y坐标
nextIndex: -1, //下一个切换的标签下标
moved: false, //是否为一次水平滑动
startTimestamp: 0,
};
},
methods: {
touchStart(event) {
if (!this.parent.swipeable) return;
this.resetTouchStatus();
this.startX = event.touches[0].clientX;
this.startY = event.touches[0].clientY;
this.startTimestamp = now();
},
touchMove(event) {
if (!this.parent.swipeable) return;
const touch = event.touches[0];
this.deltaX = touch.clientX < 0 ? 0 : this.startX - touch.clientX;
this.deltaY = this.startY - touch.clientY;
const offsetX = Math.abs(this.deltaX);
const offsetY = Math.abs(this.deltaY);
// 当距离大于某个值时锁定方向
if (!this.direction || (offsetX < 10 && offsetY < 10)) this.direction = getDirection(offsetX, offsetY);
if (this.direction === "horizontal") { //水平滑动
const { dataLen, contentWidth, currentIndex, tabs, swipeAnimated } = this.parent;
const isRight = this.deltaX < 0; //判断是否向右滑动
// 如果为第一页,则不允许向右滑;为最后一页,则不允许左滑
if ((isRight && currentIndex === 0) || (!isRight && currentIndex === dataLen - 1)) return;
this.nextIndex = currentIndex + (isRight ? -1 : 1); //下一个标签
if (tabs[this.nextIndex]?.disabled) return; //禁用的标签不允许滑动
this.moved = true; //标记为一次水平滑动
// 改变标签内容滑动轨道样式,模拟拖动动画效果
if (swipeAnimated) {
const offsetWidth = contentWidth * currentIndex * -1 + offsetX * (isRight ? 1 : -1);
this.parent.changeTrackStyle(true, 0, offsetWidth);
this.parent.setDx(this.deltaX, false);
}
}
},
touchEnd() {
if (this.moved) {
// 何时可切换标签,当横向滑动距离大于设定阈值,或快速滑动(300ms内)切滑动距离大于18px时
const deltaTime = now() - this.startTimestamp;
const distance = Math.abs(this.deltaX);
const speed = (distance / deltaTime).toFixed(4);
const isChange = speed > 0.25 || distance >= this.parent.swipeThreshold;//是否切换
const currIndex = this.parent.currentIndex; //当前选中下标
const targetIndex = isChange ? this.nextIndex : currIndex; //目标标签的下标
this.parent.touchEndForPane(this.deltaX, currIndex, targetIndex, isChange);
}
},
// 重置触摸状态
resetTouchStatus() {
this.direction = '';
this.deltaX = 0;
this.deltaY = 0;
this.nextIndex = -1;
this.moved = false;
this.startTimestamp = 0;
},
}
}

View File

@ -0,0 +1,177 @@
/**
* 判断传入的值是否为空
* @param {*} val
* @returns
*/
export function isNull(val) {
if (typeof val == "boolean") {
return false;
}
if (typeof val == "number") {
return false;
}
if (val instanceof Array) {
if (val.length == 0) return true;
} else if (val instanceof Object) {
if (JSON.stringify(val) === "{}") return true;
} else {
if (
val == "null" ||
val == null ||
val == "undefined" ||
val == undefined ||
val == ""
)
return true;
return false;
}
return false;
}
// 不为空
export function isDef(val) {
return val !== undefined && val !== null;
}
// 是否是一个数字
export function isNumeric(val) {
return /^\d+(\.\d+)?$/.test(val);
}
// 是一个对象
export function isObject(val) {
return val !== null && typeof val === 'object';
}
// 是一个字符串
export function isString(val) {
return Object.prototype.toString.call(val) === "[object String]"
}
// 空操作
export function noop() {}
// 是一个函数
export function isFunction(val) {
return typeof val === 'function';
}
// 是一个promise对象
export function isPromise(val) {
return isObject(val) && isFunction(val.then) && isFunction(val.catch);
}
// 添加单位
export function addUnit(value) {
if (!isDef(value)) {
return undefined;
}
value = String(value);
return isNumeric(value) ? `${value}px` : value;
}
// 调用拦截器
export function callInterceptor(options) {
const {
interceptor,
args,
done
} = options;
if (interceptor) {
const returnVal = interceptor(...args);
if (isPromise(returnVal)) {
returnVal.then((value) => {
if (value) done();
}).catch(noop);
} else if (returnVal) {
done();
}
} else {
done();
}
}
const rgbaRegex = /^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d*(?:\.\d+)?)\)$/;
export const getColor = function(colorStr) {
const matches = colorStr.match(rgbaRegex);
if (matches && matches.length === 5) {
return [
matches[1],
matches[2],
matches[3],
matches[4]
];
}
return [];
};
export function toClass(classObj, ...classArray) {
const arr = Object.keys(classObj || {}).filter(key => classObj[key])
arr.push(...classArray)
return arr.join(" ")
}
// 判断是水平滑动还是垂直滑动
export function getDirection(x, y) {
if (x > y) return 'horizontal';
if (y > x) return 'vertical';
return '';
}
// 缓动函数
function easingFunction(time, duration, type = "linear") {
let pos = time / duration;
let value = 0;
switch (type) {
case "easeOutCubic":
value = (Math.pow((pos - 1), 3) + 1)
break;
case "easeInOutCubic":
if ((pos /= 0.5) < 1) value = 0.5 * Math.pow(pos, 3);
else value = 0.5 * (Math.pow((pos - 2), 3) + 2);
break;
default: //linear
value = pos;
break;
}
return value;
}
/**
* 进度函数
* @param {Object} time 当前已经运动的时间
* @param {Object} begin 距离的初始值
* @param {Object} end 距离的结束值
* @param {Object} duration 运动时长
*/
export function progress(time, begin, end, duration, type) {
return begin + (end - begin) * easingFunction(time, duration, type);
}
let uid = 0;
export function getUid() {
return uid++
}
const hasOwnProperty = Object.prototype.hasOwnProperty
/**
* 检查对象是否具有该属性
* @param {*} obj 对象
* @param {*} key 对象属性名
* @returns
*/
export function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key)
}
export const now = Date.now || function() {
return +new Date();
};

View File

@ -0,0 +1,8 @@
export const utilMixin = function() {
return {
methods: {
},
}
}

View File

@ -0,0 +1,238 @@
<template>
<view
class="y-tab__pane"
:data-index="index"
:class="[uniquePaneClass, paneClass]"
:style="[paneStyle]"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
>
<!-- 渲染过的则不再渲染未渲染的根据激活状态进行渲染 -->
<view class="y-tab__pane--wrap" v-if="rendered ? true : active">
<slot></slot>
</view>
</view>
</template>
<script>
import { isNull, toClass, getUid } from '../js/uitls';
import { touchMixin } from '../js/touchMixin';
import { options } from '../js/const';
export default {
name: 'yTab',
mixins: [touchMixin],
options,
props: {
title: String, //
disabled: Boolean, //
dot: Boolean, //
badge: {
type: [Number, String],
default: '',
}, //
// ,badgeMaxCount+,
badgeMaxCount: {
type: [Number, String],
default: 99,
},
name: [Number, String], //
titleStyle: Object, //
titleClass: String, //
iconType: String, //uniappuni-uiuni-iconstypecustomPrefix
iconSize: {
type: [Number, String],
default: 16,
}, //
customPrefix: String, //
imageSrc: String, //
imageMode: {
type: String,
default: 'scaleToFill',
validator(value) {
return [
'scaleToFill',
'aspectFit',
'aspectFill',
'widthFix',
'heightFix',
'top',
'bottom',
'center',
'left',
'right',
'top left',
'top right',
'bottom left',
'bottom right',
].includes(value);
},
}, //uniapp->>imagemode
position: {
type: String,
default: 'right',
validator(value) {
return ['top', 'bottom', 'left', 'right'].includes(value);
},
}, //
},
data() {
return {
isUnmounted: false,
index: -1, //
parent: null, //
active: false, //
rendered: false, //
swipeable: false, //
paneStyle: null, //
scrollspy: false, //
paneObserver: null, //pane
isDisjoint: false, //pane
isActiveLast: false, // pane
};
},
computed: {
computedName() {
return !isNull(this.name) ? this.name : this.index;
},
unqieKey() {
return getUid();
},
//
uniquePaneClass() {
return 'y-tab__pane' + this.unqieKey;
},
// class
paneClass() {
return toClass({ 'is-active': this.active, 'is-scrollspy': this.scrollspy });
},
},
watch: {
$props: {
deep: true,
// immediate: true,
handler(newValue, oldValue) {
// tab
if (this.parent) {
this.parent.updateTab({
newValue: { ...newValue, badge: this.formatBadge() },
oldValue: oldValue && { ...oldValue },
index: this.index,
});
}
},
},
},
created() {
this.parent = this.getParent();
if (this.parent) {
this.parent.children.push(this);
this.parent.putTab({ newValue: { ...this.$props, key: this.unqieKey, badge: this.formatBadge() } });
this.scrollspy = this.parent.scrollspy;
this.rendered = !this.parent.isLazyRender || this.parent.scrollspy; //
}
},
// #ifndef VUE3
destroyed() {
if (this.isUnmounted) return;
this.unInit();
},
// #endif
// #ifdef VUE3
unmounted() {
this.isUnmounted = true;
this.unInit();
},
// #endif
methods: {
//
formatBadge() {
if (!isNull(this.badge) && !isNull(this.badgeMaxCount) && this.badge > this.badgeMaxCount) {
return this.badgeMaxCount + '+';
} else {
return this.badge;
}
},
//
getSelectorQuery() {
let query = null;
// #ifdef MP-ALIPAY
query = uni.createSelectorQuery();
// #endif
// #ifndef MP-ALIPAY
query = uni.createSelectorQuery().in(this);
// #endif
return query;
},
//
getRect(selector) {
return new Promise((resolve, reject) => {
selector = `.${this.uniquePaneClass}` + (!isNull(selector) ? ' ' + selector : '');
this.getSelectorQuery()
.select(selector)
.boundingClientRect()
.exec((rect) => {
resolve(rect[0] || {});
});
});
},
//
unInit() {
this.disconnectObserver(); //
if (this.parent) {
const index = this.parent.children.findIndex((item) => item === this);
this.parent.children.splice(index, 1);
this.parent.tabs.splice(index, 1);
this.parent.tabRects.splice(index, 1);
}
},
//
getParent(name = 'yTabs') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false;
parentName = parent.$options.name;
}
return parent;
},
//
disconnectObserver() {
this.paneObserver && this.paneObserver?.disconnect();
},
// -
async observePane(top) {
this.disconnectObserver();
const paneObserver = uni.createIntersectionObserver(this, { thresholds: [0, 0.01, 0.99, 1] });
// y-tabs使pane
// y-tabs__contenttoponPageScroll使resizepane
// paneuniappAndroidscroll-view使scroll-view
paneObserver.relativeToViewport({ top: -top }); //
// unk-vendors.js:14596 [system] Node .y-tab__pane9 is not found. Intersection observer will not trigger.
paneObserver.observe(`.${this.uniquePaneClass} .y-tab__pane--wrap`, (res) => {
// console.log('res:', this.title, res);
if (!this.isActiveLast) {
// toptop,intersectionRatio0
this.isDisjoint = res.intersectionRatio <= 0 && res.boundingClientRect.top < res.relativeRect.top;
} else {
// pane
this.isDisjoint = res.intersectionRatio > 0 && res.boundingClientRect.bottom <= res.relativeRect.bottom;
}
// tabsinit使:
//
if (this.parent.isLoaded && !this.parent.lockedScrollspy) this.parent.setActivedIndexToScroll();
});
this.paneObserver = paneObserver;
},
},
};
</script>
<style lang="scss" scoped>
@import '../css/index';
</style>

File diff suppressed because it is too large Load Diff