Merge branch 'develop' of https://gitee.com/likeadmin/likeadmin_java into develop

This commit is contained in:
TinyAnts 2022-09-08 11:43:30 +08:00
commit 1cde7463fe
4 changed files with 580 additions and 0 deletions

View File

@ -0,0 +1,79 @@
<template>
<view :class="{ active, inactive: !active, tab: true }" :style="shouldShow ? '' : 'display: none;'">
<slot v-if="shouldRender"></slot>
</view>
</template>
<script lang="ts" setup>
import { ref, provide, inject, watch, computed, onMounted, getCurrentInstance } from "vue"
const props = withDefaults(defineProps < {
dot ? : boolean | string
name ? : boolean | string
info ? : any
} > (), {
dot: false,
name: ''
})
const active = ref<boolean>(false)
const shouldShow = ref<boolean>(false)
const shouldRender = ref<boolean>(false)
const inited = ref(undefined)
const updateTabs: any = inject('updateTabs')
const handleChange: any = inject('handleChange')
const updateRender = (value) => {
inited.value = inited.value || value;
active.value = value
shouldRender.value = inited.value
shouldShow.value = value
}
const update = () => {
if (updateTabs) {
updateTabs();
}
}
const { ctx } = getCurrentInstance()
handleChange(ctx, updateRender)
onMounted(() => {
update()
})
const changeData = computed(() => {
const {
dot,
info,
} = props;
return {
dot,
info,
}
})
watch(() => changeData.value, () => {
update()
})
watch(() => props.name, (val) => {
update()
})
</script>
<style>
.tab.active {
height: auto;
}
.tab.inactive {
height: 0;
overflow: visible;
}
</style>

View File

@ -0,0 +1,405 @@
<template>
<view class="tabs">
<u-sticky :enable="isFixed" :bg-color="stickyBgColor" :offset-top="top" :h5-nav-height="0">
<view :id="id" :style="{
background: bgColor
}">
<scroll-view :style="{height: height + 'rpx'}" scroll-x class="scroll-view" :scroll-left="scrollLeft"
scroll-with-animation>
<view class="scroll-box" :class="{'tabs-scorll-flex': !isScroll}">
<view class="tab-item line1" :id="'tab-item-' + index" v-for="(item, index) in list"
:key="index" @tap="clickTab(index)" :style="[tabItemStyle(index)]">
<u-badge :count="item[count] || item['dot'] || 0" :offset="offset" size="mini"></u-badge>
{{ item[name] || item['name']}}
</view>
<view v-if="showBar" class="tab-bar" :style="[tabBarStyle]"></view>
</view>
</scroll-view>
</view>
</u-sticky>
<view class="tab-content" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchcancel="onTouchEnd" @touchend="onTouchEnd">
<!-- <view class="tab-track" :class="{'tab-animated': animated}" :style="[trackStyle]"> -->
<view>
<slot></slot>
</view>
<!-- </view> -->
</view>
</view>
</template>
<script lang="ts" setup>
import { getRect } from '@/utils/util'
import { ref, reactive, computed, watch, provide, nextTick, onMounted, getCurrentInstance } from "vue"
import { useTouch } from "@/hooks/useTouch"
// Touch
const { touch, resetTouchStatus, touchStart, touchMove } = useTouch()
const emit = defineEmits<{
(event: 'change', value: number): void
}>()
const props = withDefaults(defineProps < {
isScroll ? : boolean // 23使flextab
current ? : number | string // tab
height ? : number | string //
fontSize ? : number | string //
duration ? : number | string // , ms
activeColor ? : number | string //
inactiveColor ? : number | string //
barWidth ? : number | string // barrpx
barHeight ? : number // bar
gutter ? : number | string // tab
bgColor ? : number | string //
name ? : string // (tab)
count ? : string // ()
offset ? : number[] //
bold ? : boolean // tab
activeItemStyle ? : any // tab item
showBar ? : boolean //
barStyle ? : any //
itemWidth ? : string //
isFixed ? : boolean //
top ? : number | string //
stickyBgColor ? : string //
swipeable: boolean //
// animated: boolean //
} > (), {
isScroll: true,
current: 0,
height: 80,
fontSize: 28,
duration: 0.3,
activeColor: '#2073F4',
inactiveColor: '#333',
barWidth: 40,
barHeight: 4,
gutter: 30,
bgColor: '#FFFFFF',
name: 'name',
count: 'count',
offset: [5, 20],
bold: true,
activeItemStyle: {},
showBar: true,
barStyle: {},
itemWidth: 'auto',
isFixed: false,
top: 0,
stickyBgColor: '#FFFFFF',
swipeable: true,
// animated: true
})
const list = ref<any>([])
const childrens = ref<any>([])
const scrollLeft = ref<number>(0) // scroll-view
const tabQueryInfo = ref<any>([]) // tab
const componentWidth = ref<number>(0) // px
const scrollBarLeft = ref<number>(0) // bartranslateX()
const parentLeft = ref<number>(0) // (tabs)
const id = ref<string>('cu-tab') // id
const currentIndex = ref<any>(props.current)
const barFirstTimeMove = ref<boolean>(true)// ()
const swiping = ref<boolean>(false)
//@ts-ignore
const { ctx } = getCurrentInstance()
// tabtab使
// applist
watch(() => list.value, async (n, o) => {
// list
if(!barFirstTimeMove.value && n.length !== o.length) {
currentIndex.value = 0;
}
// $nextTicktabtab
await nextTick();
init();
})
watch(() => props.current, (nVal, oVal) => {
//
nextTick(() => {
currentIndex.value = nVal;
scrollByIndex();
});
}, { immediate: true })
// bar
const tabBarStyle = computed(() => {
let style = {
width: props.barWidth + 'rpx',
transform: `translate(${scrollBarLeft.value}px, -100%)`,
//
'transition-duration': `${barFirstTimeMove.value ? 0 : props.duration }s`,
'background-color': props.activeColor,
height: props.barHeight + 'rpx',
opacity: barFirstTimeMove.value ? 0 : 1,
//
'border-radius': `${props.barHeight / 2}px`
};
Object.assign(style, props.barStyle);
return style;
})
// tab
const tabItemStyle = computed(() => {
return (index) => {
let style: any = {
height: props.height + 'rpx',
'line-height': props.height + 'rpx',
'font-size': props.fontSize + 'rpx',
padding: props.isScroll ? `0 ${props.gutter}rpx` : '',
flex: props.isScroll ? 'auto' : '1',
width: `${props.itemWidth}rpx`
};
//
if (index == currentIndex.value && props.bold) style.fontWeight = 'bold';
if (index == currentIndex.value) {
style.color = props.activeColor;
// tab item
style = Object.assign(style, props.activeItemStyle);
} else {
style.color = props.inactiveColor;
}
return style;
}
})
// const trackStyle = computed(() => {
// if (!props.animated) return ''
// return {
// left: -100 * currentIndex.value + '%',
// 'transition-duration': props.duration + 's',
// '-webkit-transition-duration': props.duration + 's',
// }
// })
const updateTabs = () => {
list.value = childrens.value.map((item) => {
const {
name,
dot,
active,
inited,
} = item.event
const { updateRender } = item
return {
name,
dot,
active,
inited,
updateRender
}
})
// nextTick(() => {
// init()
// })
}
// init便
const init = async () => {
// tabs
let tabRect = await getRect('#' + id.value, false, ctx);
// tabs
parentLeft.value = tabRect.left;
// tabs
componentWidth.value = tabRect.width;
getTabRect();
}
// tab
const clickTab = (index) => {
// tab
if (index == currentIndex.value) return;
nextTick(() => {
currentIndex.value = index;
scrollByIndex();
});
//
emit('change', index);
}
// tab
const getTabRect = () => {
//
let query: any = uni.createSelectorQuery().in(ctx);
// tab使exec()
for (let i = 0; i < list.value.length; i++) {
// sizerect
query.select(`#tab-item-${i}`).fields({
size: true,
rect: true
});
}
//
query.exec((res) => {
tabQueryInfo.value = res;
// bar
scrollByIndex();
});
}
// scroll-viewtab
const scrollByIndex = () => {
// tabtabwidthleft()
let tabInfo = tabQueryInfo.value[currentIndex.value];
if (!tabInfo) return;
// tab
let tabWidth = tabInfo.width;
// itemtabsitemlefttabsleft
let offsetLeft = tabInfo.left - parentLeft.value;
// tabs-itemscroll-view
let scrollLefts = offsetLeft - (componentWidth.value - tabWidth) / 2;
scrollLeft.value = scrollLefts < 0 ? 0 : scrollLefts;
// item
let left = tabInfo.left + tabInfo.width / 2 - parentLeft.value;
// item
scrollBarLeft.value = left - uni.upx2px(props.barWidth) / 2;
// barFirstTimeMovetruefalse
// scrollBarLeftcomputed
if (barFirstTimeMove.value == true) {
setTimeout(() => {
barFirstTimeMove.value = false;
}, 100)
}
//
childrens.value.forEach((item, ind) => {
let active = ind === currentIndex.value;
if (active !== item.event.active || !item.event.inited) {
item.updateRender(active);
}
});
}
//
const handleChange = (event, updateRender) => {
childrens.value.push({event: event, updateRender})
}
//
const onTouchStart = (event) => {
if (!props.swipeable)
return;
swiping.value = true;
touchStart(event);
}
//
const onTouchMove = (event) => {
if (!props.swipeable || !swiping.value)
return;
touchMove(event);
}
//
const onTouchEnd = () => {
if (!props.swipeable || !swiping.value)
return;
const minSwipeDistance = 50;
if (touch.direction === 'horizontal' && touch.offsetX >= minSwipeDistance) {
let index, len = list.value.length, curIndex = currentIndex.value;
if (touch.deltaX <= 0) {
curIndex >= (len - 1) ? index = 0 : index = curIndex + 1
} else {
curIndex <= 0 ? index = (len - 1) : index = curIndex - 1
}
nextTick(() => {
currentIndex.value = index;
scrollByIndex();
});
//
emit('change', index);
}
swiping.value = false;
}
onMounted(() => {
updateTabs();
})
provide('handleChange', handleChange)
provide('updateTabs', updateTabs)
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
::-webkit-scrollbar,
::-webkit-scrollbar,
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.scroll-box {
height: 100%;
position: relative;
/* #ifdef MP-TOUTIAO */
white-space: nowrap;
/* #endif */
}
.tab-fixed {
position: sticky;
top: 0;
width: 100%;
}
/* #ifdef H5 */
// 穿H5scroll-view
scroll-view ::v-deep ::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.scroll-view {
width: 100%;
white-space: nowrap;
position: relative;
}
.tab-item {
position: relative;
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
text-align: center;
transition-property: background-color, color;
}
.tab-bar {
position: absolute;
bottom: 6rpx;
}
.tabs-scorll-flex {
display: flex;
justify-content: space-between;
}
// .tab-content {
// overflow: hidden;
// .tab-track {
// position: relative;
// width: 100%;
// height: 100%;
// }
// .tab-animated {
// display: flex;
// transition-property: left;
// }
// }
</style>

73
app/src/hooks/useTouch.ts Normal file
View File

@ -0,0 +1,73 @@
import { reactive } from "vue"
/**
* @description
* @return { Function }
*/
export function useTouch () {
// 最小移动距离
const MIN_DISTANCE = 10;
const touch = reactive({
direction: '',
deltaX: 0,
deltaY: 0,
offsetX: 0,
offsetY: 0
})
/**
* @description
* @return { string }
*/
const getDirection = (x: number, y: number) => {
if (x > y && x > MIN_DISTANCE) {
return 'horizontal';
}
if (y > x && y > MIN_DISTANCE) {
return 'vertical';
}
return '';
}
/**
* @description
*/
const resetTouchStatus = () => {
touch.direction = '';
touch.deltaX = 0;
touch.deltaY = 0;
touch.offsetX = 0;
touch.offsetY = 0;
}
/**
* @description
*/
const touchStart = (event: any) => {
resetTouchStatus();
const events = event.touches[0];
touch.startX = events.clientX;
touch.startY = events.clientY;
}
/**
* @description
*/
const touchMove = (event: any) => {
const events = event.touches[0];
touch.deltaX = events.clientX - touch.startX;
touch.deltaY = events.clientY - touch.startY;
touch.offsetX = Math.abs(touch.deltaX);
touch.offsetY = Math.abs(touch.deltaY);
touch.direction =
touch.direction || getDirection(touch.offsetX, touch.offsetY);
}
return {
touch,
resetTouchStatus,
touchStart,
touchMove
}
}

23
app/src/utils/util.ts Normal file
View File

@ -0,0 +1,23 @@
/**
* @description ctx
* @param { String } selector '.app' | '#app'
* @param { Boolean } all
* @param { ctx } context
*/
export const getRect = (selector: string, all: boolean = false, context?: any) => {
return new Promise((resolve, reject) => {
let qurey = uni.createSelectorQuery()
if (context) {
qurey = uni.createSelectorQuery().in(context)
}
qurey[all ? 'selectAll' : 'select'](selector).boundingClientRect(function(rect) {
if (all && Array.isArray(rect) && rect.length) {
return resolve(rect)
}
if (!all && rect) {
return resolve(rect)
}
reject('找不到元素')
}).exec()
})
}