优化组件/更新

This commit is contained in:
gyq
2025-12-03 10:13:55 +08:00
parent 92f9776a35
commit 09b6e36a52
261 changed files with 22080 additions and 7238 deletions

View File

@@ -7,25 +7,21 @@
</view>
</view> -->
<view class="u-menu-wrap">
<scroll-view scroll-y scroll-with-animation class="u-tab-view menu-scroll-view" :scroll-top="scrollTop"
:scroll-into-view="itemId">
<view v-for="(item,index) in tabbar" :key="index" class="u-tab-item"
:class="[current == index ? 'u-tab-item-active' : '']" @tap.stop="swichMenu(index)">
<text class="u-line-1">{{item.title}}</text>
<scroll-view scroll-y scroll-with-animation class="u-tab-view menu-scroll-view" :scroll-top="scrollTop" :scroll-into-view="itemId">
<view v-for="(item, index) in tabbar" :key="index" class="u-tab-item" :class="[current == index ? 'u-tab-item-active' : '']" @tap.stop="swichMenu(index)">
<text class="u-line-1">{{ item.title }}</text>
</view>
</scroll-view>
<scroll-view :scroll-top="scrollRightTop" scroll-y scroll-with-animation class="right-box"
@scroll="rightScroll">
<scroll-view :scroll-top="scrollRightTop" scroll-y scroll-with-animation class="right-box" @scroll="rightScroll">
<view class="page-view">
<view class="class-item" :id="'item' + index" v-for="(item , index) in tabbar" :key="index">
<view class="item-title color-main">
<text>{{item.title}}</text>
<view class="class-item" :id="'item' + index" v-for="(item, index) in tabbar" :key="index">
<view class="item-title">
<text>{{ item.title }}</text>
</view>
<view class="item-container u-m-t-32">
<view class="thumb-box" @click="toPage(item1, index1)"
v-for="(item1, index1) in item.children" :key="index1">
<view class="thumb-box" @click="toPage(item1, index1)" v-for="(item1, index1) in item.children" :key="index1">
<image class="item-menu-image" :src="item1.miniIcon" mode=""></image>
<view class="item-menu-name u-m-t-16">{{item1.title}}</view>
<view class="item-menu-name u-m-t-16">{{ item1.title }}</view>
</view>
</view>
</view>
@@ -35,125 +31,137 @@
</view>
</template>
<script>
import {
useMenusStore
} from '@/store/menus.js';
const menusStore = useMenusStore()
export default {
data() {
return {
scrollTop: 0, //tab标题的滚动条位置
oldScrollTop: 0,
current: 0, // 预设当前项的值
menuHeight: 0, // 左边菜单的高度
menuItemHeight: 0, // 左边菜单item的高度
itemId: '', // 栏目右边scroll-view用于滚动的id
tabbar: [],
menuItemPos: [],
arr: [],
scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
timer: null, // 定时器
isTabClickOver: true
import { useMenusStore } from '@/store/menus.js';
const menusStore = useMenusStore();
export default {
data() {
return {
scrollTop: 0, //tab标题的滚动条位置
oldScrollTop: 0,
current: 0, // 预设当前项的值
menuHeight: 0, // 左边菜单的高度
menuItemHeight: 0, // 左边菜单item的高度
itemId: '', // 栏目右边scroll-view用于滚动的id
tabbar: [],
menuItemPos: [],
arr: [],
scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
timer: null, // 定时器
isTabClickOver: true
};
},
onLoad() {
this.init();
},
onReady() {
this.getMenuItemTop();
},
methods: {
toPage(item, index) {
console.log(item);
if (item.miniPath == 'pages/permission/secondary_page') {
uni.setStorageSync('secondaryPageMenus', item);
}
uni.navigateTo({
url: '/' + item.miniPath.replace(/^\/+|\/+$/g, ''),
fail(err) {
uni.showToast({
icon: 'none',
title: '无该页面路径或无权限访问该页面'
});
}
});
},
onLoad() {
this.init()
},
onReady() {
this.getMenuItemTop()
},
methods: {
toPage(item, index) {
console.log(item);
uni.navigateTo({
url: '/' + item.miniPath.replace(/^\/+|\/+$/g, ''),
fail(err) {
uni.showToast({
icon: 'none',
title: '无该页面路径或无权限访问该页面'
})
}
})
},
async init() {
const res = await menusStore.getMenus()
this.tabbar = res.filter(v => v.type == 0 && v.children.length && !v.hidden).map(v => {
async init() {
const res = await menusStore.getMenus();
this.tabbar = res
.filter((v) => v.type == 0 && v.children.length && !v.hidden)
.map((v) => {
return {
...v,
children: v.children.filter(child => child.type == 0 && !child.hidden)
}
})
console.log(this.tabbar);
},
// 点击左边的栏目切换
async swichMenu(index) {
if (this.arr.length == 0) {
await this.getMenuItemTop();
}
if (index == this.current) return;
this.isTabClickOver = false;
this.scrollRightTop = this.oldScrollTop;
this.$nextTick(function() {
this.scrollRightTop = this.arr[index];
this.current = index;
this.leftMenuStatus(index);
})
},
// 获取一个目标元素的高度
getElRect(elClass, dataVal) {
new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(this);
query.select('.' + elClass).fields({
size: true
}, res => {
// 如果节点尚未生成res值为null循环调用执行
if (!res) {
setTimeout(() => {
this.getElRect(elClass);
}, 10);
return;
children: v.children.filter((child) => child.type == 0 && !child.hidden)
};
});
console.log(this.tabbar);
},
// 点击左边的栏目切换
async swichMenu(index) {
if (this.arr.length == 0) {
await this.getMenuItemTop();
}
if (index == this.current) return;
this.isTabClickOver = false;
this.scrollRightTop = this.oldScrollTop;
this.$nextTick(function () {
this.scrollRightTop = this.arr[index];
this.current = index;
this.leftMenuStatus(index);
});
},
// 获取一个目标元素的高度
getElRect(elClass, dataVal) {
new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(this);
query
.select('.' + elClass)
.fields(
{
size: true
},
(res) => {
// 如果节点尚未生成res值为null循环调用执行
if (!res) {
setTimeout(() => {
this.getElRect(elClass);
}, 10);
return;
}
this[dataVal] = res.height;
resolve();
}
this[dataVal] = res.height;
resolve();
}).exec();
})
},
// 观测元素相交状态
async observer() {
this.tabbar.map((val, index) => {
let observer = uni.createIntersectionObserver(this);
// 检测右边scroll-view的id为itemxx的元素与right-box的相交状态
// 如果跟.right-box底部相交就动态设置左边栏目的活动状态
observer.relativeTo('.right-box', {
)
.exec();
});
},
// 观测元素相交状态
async observer() {
this.tabbar.map((val, index) => {
let observer = uni.createIntersectionObserver(this);
// 检测右边scroll-view的id为itemxx的元素与right-box的相交状态
// 如果跟.right-box底部相交就动态设置左边栏目的活动状态
observer
.relativeTo('.right-box', {
top: 0
}).observe('#item' + index, res => {
})
.observe('#item' + index, (res) => {
if (res.intersectionRatio > 0) {
let id = res.id.substring(4);
this.leftMenuStatus(id);
}
})
})
},
// 设置左边菜单的滚动状态
async leftMenuStatus(index) {
if (!this.isTabClickOver) {
return;
}
this.current = index;
// 如果为0意味着尚未初始化
if (this.menuHeight == 0 || this.menuItemHeight == 0) {
await this.getElRect('menu-scroll-view', 'menuHeight');
await this.getElRect('u-tab-item', 'menuItemHeight');
}
// 将菜单活动item垂直居中
this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2;
},
// 获取右边菜单每个item到顶部的距离
getMenuItemTop() {
new Promise(resolve => {
let selectorQuery = uni.createSelectorQuery();
selectorQuery.selectAll('.class-item').boundingClientRect((rects) => {
});
});
},
// 设置左边菜单的滚动状态
async leftMenuStatus(index) {
if (!this.isTabClickOver) {
return;
}
this.current = index;
// 如果为0意味着尚未初始化
if (this.menuHeight == 0 || this.menuItemHeight == 0) {
await this.getElRect('menu-scroll-view', 'menuHeight');
await this.getElRect('u-tab-item', 'menuItemHeight');
}
// 将菜单活动item垂直居中
this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2;
},
// 获取右边菜单每个item到顶部的距离
getMenuItemTop() {
new Promise((resolve) => {
let selectorQuery = uni.createSelectorQuery();
selectorQuery
.selectAll('.class-item')
.boundingClientRect((rects) => {
// 如果节点尚未生成rects值为[](因为用selectAll所以返回的是数组),循环调用执行
if (!rects.length) {
setTimeout(() => {
@@ -165,172 +173,174 @@
// 这里减去rects[0].top是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
this.arr.push(rect.top - rects[0].top);
resolve();
})
}).exec()
})
},
// 右边菜单滚动
async rightScroll(e) {
this.oldScrollTop = e.detail.scrollTop;
if (this.arr.length == 0) {
await this.getMenuItemTop();
}
if (this.timer) return;
if (!this.menuHeight) {
await this.getElRect('menu-scroll-view', 'menuHeight');
}
if (e.detail.scrollTop == 0) {
this.isTabClickOver = true;
// return swichMenu(0)
}
setTimeout(() => { // 节流
this.timer = null;
// scrollHeight为右边菜单垂直中点位置
let scrollHeight = e.detail.scrollTop + this.menuHeight / 2;
for (let i = 0; i < this.arr.length; i++) {
let height1 = this.arr[i];
let height2 = this.arr[i + 1];
// 如果不存在height2意味着数据循环已经到了最后一个设置左边菜单为最后一项即可
if (!height2 || scrollHeight >= height1 && scrollHeight < height2) {
if (this.isTabClickOver) {
this.leftMenuStatus(i);
} else {
this.isTabClickOver = true;
}
return;
}
}
}, 10)
});
})
.exec();
});
},
// 右边菜单滚动
async rightScroll(e) {
this.oldScrollTop = e.detail.scrollTop;
if (this.arr.length == 0) {
await this.getMenuItemTop();
}
if (this.timer) return;
if (!this.menuHeight) {
await this.getElRect('menu-scroll-view', 'menuHeight');
}
if (e.detail.scrollTop == 0) {
this.isTabClickOver = true;
// return swichMenu(0)
}
setTimeout(() => {
// 节流
this.timer = null;
// scrollHeight为右边菜单垂直中点位置
let scrollHeight = e.detail.scrollTop + this.menuHeight / 2;
for (let i = 0; i < this.arr.length; i++) {
let height1 = this.arr[i];
let height2 = this.arr[i + 1];
// 如果不存在height2意味着数据循环已经到了最后一个设置左边菜单为最后一项即可
if (!height2 || (scrollHeight >= height1 && scrollHeight < height2)) {
if (this.isTabClickOver) {
this.leftMenuStatus(i);
} else {
this.isTabClickOver = true;
}
return;
}
}
}, 10);
}
}
};
</script>
<style lang="scss" scoped>
.u-wrap {
height: calc(100vh);
/* #ifdef H5 */
height: calc(100vh - var(--window-top));
/* #endif */
display: flex;
flex-direction: column;
}
.u-wrap {
height: calc(100vh);
/* #ifdef H5 */
height: calc(100vh - var(--window-top));
/* #endif */
display: flex;
flex-direction: column;
}
.u-search-box {
padding: 18rpx 30rpx;
}
.u-search-box {
padding: 18rpx 30rpx;
}
.u-menu-wrap {
flex: 1;
display: flex;
overflow: hidden;
}
.u-menu-wrap {
flex: 1;
display: flex;
overflow: hidden;
}
.u-search-inner {
background-color: rgb(234, 234, 234);
border-radius: 100rpx;
display: flex;
align-items: center;
padding: 10rpx 16rpx;
}
.u-search-inner {
background-color: rgb(234, 234, 234);
border-radius: 100rpx;
display: flex;
align-items: center;
padding: 10rpx 16rpx;
}
.u-search-text {
font-size: 26rpx;
color: $u-tips-color;
margin-left: 10rpx;
}
.u-search-text {
font-size: 26rpx;
color: $u-tips-color;
margin-left: 10rpx;
}
.u-tab-view {
width: 240rpx;
height: 100%;
}
.u-tab-view {
width: 240rpx;
height: 100%;
}
.u-tab-item {
height: 110rpx;
background: #f6f6f6;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #444;
font-weight: 400;
line-height: 1;
}
.u-tab-item {
height: 110rpx;
background: #f6f6f6;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #444;
font-weight: 400;
line-height: 1;
}
.u-tab-item-active {
position: relative;
color: $my-main-color;
font-size: 30rpx;
font-weight: 600;
background: #fff;
}
.u-tab-item-active {
position: relative;
color: $my-main-color;
font-size: 30rpx;
font-weight: 600;
background: #fff;
}
.u-tab-item-active::before {
content: "";
position: absolute;
border-left: 4px solid $u-primary;
left: 0;
top: 0;
bottom: 0;
}
.u-tab-item-active::before {
content: '';
position: absolute;
border-left: 4px solid $u-primary;
left: 0;
top: 0;
bottom: 0;
}
.u-tab-view {
height: 100%;
background-color: #f6f6f6;
}
.u-tab-view {
height: 100%;
background-color: #f6f6f6;
}
.right-box {
// background-color: rgb(250, 250, 250);
}
.right-box {
// background-color: rgb(250, 250, 250);
}
.page-view {
padding: 30rpx 0;
}
.page-view {
padding: 30rpx 0;
}
.class-item {
margin-bottom: 30rpx;
background-color: #fff;
border-radius: 8rpx;
margin-left: 24rpx;
}
.class-item {
margin-bottom: 30rpx;
background-color: #fff;
border-radius: 8rpx;
margin-left: 24rpx;
}
.class-item:last-child {
min-height: 100vh;
}
.class-item:last-child {
min-height: 100vh;
}
.item-title {
font-size: 28rpx;
font-weight: bold;
}
.item-title {
font-size: 28rpx;
font-weight: bold;
}
.item-menu-name {
font-weight: normal;
font-size: 20rpx;
color: #333;
}
.item-menu-name {
font-weight: normal;
font-size: 20rpx;
color: #333;
}
.item-container {
display: flex;
flex-wrap: wrap;
}
.item-container {
display: flex;
flex-wrap: wrap;
}
.thumb-box {
width: 158rpx;
height: 170rpx;
background: #FFFFFF;
box-shadow: 0rpx 4rpx 6rpx 2rpx rgba(0, 0, 0, 0.08);
border-radius: 12rpx 12rpx 12rpx 12rpx;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-top: 20rpx;
margin-right: 24rpx;
}
.thumb-box {
width: 158rpx;
height: 170rpx;
// background: #ffffff;
// box-shadow: 0rpx 4rpx 6rpx 2rpx rgba(0, 0, 0, 0.08);
// border-radius: 12rpx 12rpx 12rpx 12rpx;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-top: 20rpx;
margin-right: 24rpx;
}
.item-menu-image {
width: 64rpx;
height: 64rpx;
}
</style>
.item-menu-image {
width: 94rpx;
height: 94rpx;
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<view class="container">
<view class="row">
<view class="header">
<text class="t">{{ menus.title }}</text>
</view>
<view class="menu-wrap">
<view class="item" v-for="(val, i) in menus.children" :key="i" @click="to(val)">
<image :src="val.miniIcon" mode="aspectFit" class="icon"></image>
<view class="info">
<view class="title">
<text class="t">{{ val.title }}</text>
</view>
<view class="intro">
<text class="t">{{ val.intro || '-' }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
function to(item) {
uni.navigateTo({
url: item.miniPath
});
}
const menus = ref({});
onLoad(() => {
let pathObj = uni.getStorageSync('secondaryPageMenus');
pathObj.children = pathObj.children.filter((item) => {
return item.miniPath !== '';
});
console.log(pathObj);
menus.value = pathObj;
});
</script>
<style>
page {
background-color: #f8f8f8;
}
</style>
<style scoped lang="scss">
.container {
padding: 0 28upx 28upx;
.row {
.header {
padding: 28upx 0;
.t {
font-size: 32upx;
font-weight: bold;
}
}
.menu-wrap {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto;
grid-column-gap: 28upx;
grid-row-gap: 28upx;
.item {
display: flex;
background-color: #fff;
padding: 20upx;
border-radius: 20upx;
.icon {
$size: 80upx;
width: $size;
height: $size;
flex-shrink: 0;
}
.info {
display: flex;
padding-left: 20upx;
flex-direction: column;
.title {
.t {
font-size: 28upx;
font-weight: bold;
}
}
.intro {
.t {
font-size: 28upx;
color: #999;
}
}
}
}
}
}
}
</style>