From d1448cb0ef10f358bb7bddb4e1ec268515e0b787 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 15 七月 2025 11:46:57 +0800
Subject: [PATCH] 项目初始化

---
 uni_modules/uview-ui/components/u-tabs/u-tabs.vue |  354 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 354 insertions(+), 0 deletions(-)

diff --git a/uni_modules/uview-ui/components/u-tabs/u-tabs.vue b/uni_modules/uview-ui/components/u-tabs/u-tabs.vue
new file mode 100644
index 0000000..9c54cc1
--- /dev/null
+++ b/uni_modules/uview-ui/components/u-tabs/u-tabs.vue
@@ -0,0 +1,354 @@
+<template>
+	<view class="u-tabs">
+		<view class="u-tabs__wrapper">
+			<slot name="left" />
+			<view class="u-tabs__wrapper__scroll-view-wrapper">
+				<scroll-view
+					:scroll-x="scrollable"
+					:scroll-left="scrollLeft"
+					scroll-with-animation
+					class="u-tabs__wrapper__scroll-view"
+					:show-scrollbar="false"
+					ref="u-tabs__wrapper__scroll-view"
+				>
+					<view
+						class="u-tabs__wrapper__nav"
+						ref="u-tabs__wrapper__nav"
+					>
+						<view
+							class="u-tabs__wrapper__nav__item"
+							v-for="(item, index) in list"
+							:key="index"
+							@tap="clickHandler(item, index)"
+							:ref="`u-tabs__wrapper__nav__item-${index}`"
+							:style="[$u.addStyle(itemStyle), {flex: scrollable ? '' : 1}]"
+							:class="[`u-tabs__wrapper__nav__item-${index}`, item.disabled && 'u-tabs__wrapper__nav__item--disabled']"
+						>
+							<text
+								:class="[item.disabled && 'u-tabs__wrapper__nav__item__text--disabled']"
+								class="u-tabs__wrapper__nav__item__text"
+								:style="[textStyle(index)]"
+							>{{ item[keyName] }}</text>
+							<u-badge
+								:show="!!(item.badge && (item.badge.show || item.badge.isDot || item.badge.value))"
+								:isDot="item.badge && item.badge.isDot || propsBadge.isDot"
+								:value="item.badge && item.badge.value || propsBadge.value"
+								:max="item.badge && item.badge.max || propsBadge.max"
+								:type="item.badge && item.badge.type || propsBadge.type"
+								:showZero="item.badge && item.badge.showZero || propsBadge.showZero"
+								:bgColor="item.badge && item.badge.bgColor || propsBadge.bgColor"
+								:color="item.badge && item.badge.color || propsBadge.color"
+								:shape="item.badge && item.badge.shape || propsBadge.shape"
+								:numberType="item.badge && item.badge.numberType || propsBadge.numberType"
+								:inverted="item.badge && item.badge.inverted || propsBadge.inverted"
+								customStyle="margin-left: 4px;"
+							></u-badge>
+						</view>
+						<!-- #ifdef APP-NVUE -->
+						<view
+							class="u-tabs__wrapper__nav__line"
+							ref="u-tabs__wrapper__nav__line"
+							:style="[{
+									width: $u.addUnit(lineWidth),
+									height: $u.addUnit(lineHeight),
+									background: lineColor,
+									backgroundSize: lineBgSize,
+								}]"
+						>
+							<!-- #endif -->
+							<!-- #ifndef APP-NVUE -->
+							<view
+								class="u-tabs__wrapper__nav__line"
+								ref="u-tabs__wrapper__nav__line"
+								:style="[{
+										width: $u.addUnit(lineWidth),
+										transform: `translate(${lineOffsetLeft}px)`,
+										transitionDuration: `${firstTime ? 0 : duration}ms`,
+										height: $u.addUnit(lineHeight),
+										background: lineColor,
+										backgroundSize: lineBgSize,
+									}]"
+							>
+								<!-- #endif -->
+							</view>
+						</view>
+				</scroll-view>
+			</view>
+			<slot name="right" />
+		</view>
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	const animation = uni.requireNativePlugin('animation')
+	const dom = uni.requireNativePlugin('dom')
+	// #endif
+	import props from './props.js';
+	/**
+	 * Tabs 鏍囩
+	 * @description tabs鏍囩缁勪欢锛屽湪鏍囩澶氱殑鏃跺�欙紝鍙互閰嶇疆涓哄乏鍙虫粦鍔紝鏍囩灏戠殑鏃跺�欙紝鍙互绂佹婊戝姩銆� 璇ョ粍浠剁殑涓�涓壒鐐规槸閰嶇疆涓烘粴鍔ㄦā寮忔椂锛屾縺娲荤殑tab浼氳嚜鍔ㄧЩ鍔ㄥ埌缁勪欢鐨勪腑闂翠綅缃��
+	 * @tutorial https://www.uviewui.com/components/tabs.html
+	 * @property {String | Number}	duration			婊戝潡绉诲姩涓�娆℃墍闇�鐨勬椂闂达紝鍗曚綅绉掞紙榛樿 200 锛�
+	 * @property {String | Number}	swierWidth			swiper鐨勫搴︼紙榛樿 '750rpx' 锛�
+	 * @property {String}	keyName	 浠巂list`鍏冪礌瀵硅薄涓鍙栫殑閿悕锛堥粯璁� 'name' 锛�
+	 * @event {Function(index)} change 鏍囩鏀瑰彉鏃惰Е鍙� index: 鐐瑰嚮浜嗙鍑犱釜tab锛岀储寮曚粠0寮�濮�
+	 * @event {Function(index)} click 鐐瑰嚮鏍囩鏃惰Е鍙� index: 鐐瑰嚮浜嗙鍑犱釜tab锛岀储寮曚粠0寮�濮�
+	 * @example <u-tabs :list="list" :is-scroll="false" :current="current" @change="change"></u-tabs>
+	 */
+	export default {
+		name: 'u-tabs',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				firstTime: true,
+				scrollLeft: 0,
+				scrollViewWidth: 0,
+				lineOffsetLeft: 0,
+				tabsRect: {
+					left: 0
+				},
+				innerCurrent: 0,
+				moving: false,
+			}
+		},
+		watch: {
+			current: {
+				immediate: true,
+				handler (newValue, oldValue) {
+					// 鍐呭閮ㄥ�间笉鐩哥瓑鏃讹紝鎵嶅皾璇曠Щ鍔ㄦ粦鍧�
+					if (newValue !== this.innerCurrent) {
+						this.innerCurrent = newValue
+						this.$nextTick(() => {
+							this.resize()
+						})
+					}
+				}
+			},
+			// list鍙樺寲鏃讹紝閲嶆柊娓叉煋list鍚勯」淇℃伅
+			list() {
+				this.$nextTick(() => {
+					this.resize()
+				})
+			}
+		},
+		computed: {
+			textStyle() {
+				return index => {
+					const style = {}
+					// 鍙栧綋鏈熸槸鍚︽縺娲荤殑鏍峰紡
+					const customeStyle = index === this.innerCurrent ? uni.$u.addStyle(this.activeStyle) : uni.$u
+						.addStyle(
+							this.inactiveStyle)
+					// 濡傛灉褰撳墠鑿滃崟琚鐢紝鍒欏姞涓婂搴旈鑹诧紝闇�瑕佸湪姝ゅ仛澶勭悊锛屾槸鍥犱负nvue涓嬶紝鏃犳硶鍦╯tyle鏍峰紡涓�氳繃!import瑕嗙洊鏍囩鐨勫唴鑱旀牱寮�
+					if (this.list[index].disabled) {
+						style.color = '#c8c9cc'
+					}
+					return uni.$u.deepMerge(customeStyle, style)
+				}
+			},
+			propsBadge() {
+				return uni.$u.props.badge
+			}
+		},
+		async mounted() {
+			this.init()
+		},
+		methods: {
+			setLineLeft() {
+				const tabItem = this.list[this.innerCurrent];
+				if (!tabItem) {
+					return;
+				}
+				// 鑾峰彇婊戝潡璇ョЩ鍔ㄧ殑浣嶇疆
+				let lineOffsetLeft = this.list
+					.slice(0, this.innerCurrent)
+					.reduce((total, curr) => total + curr.rect.width, 0);
+                // 鑾峰彇涓嬪垝绾跨殑鏁板�紁x琛ㄧず娉�
+				const lineWidth = uni.$u.getPx(this.lineWidth);
+				this.lineOffsetLeft = lineOffsetLeft + (tabItem.rect.width - lineWidth) / 2
+				// #ifdef APP-NVUE
+				// 绗竴娆$Щ鍔ㄦ粦鍧楋紝鏃犻渶杩囨浮鏃堕棿
+				this.animation(this.lineOffsetLeft, this.firstTime ? 0 : parseInt(this.duration))
+				// #endif
+
+				// 濡傛灉鏄涓�娆℃墽琛屾鏂规硶锛岃婊戝潡鍦ㄥ垵濮嬪寲鏃讹紝鐬棿婊戝姩鍒扮涓�涓猼ab item鐨勪腑闂�
+				// 杩欓噷闇�瑕佷竴涓畾鏃跺櫒锛屽洜涓哄湪闈瀗vue涓嬶紝鏄洿鎺ラ�氳繃style缁戝畾杩囨浮鏃堕棿锛岄渶瑕佺瓑鍏惰繃娓″畬鎴愬悗锛屽啀璁剧疆涓篺alse(闈炵涓�娆$Щ鍔ㄦ粦鍧�)
+				if (this.firstTime) {
+					setTimeout(() => {
+						this.firstTime = false
+					}, 10);
+				}
+			},
+			// nvue涓嬭缃粦鍧楃殑浣嶇疆
+			animation(x, duration = 0) {
+				// #ifdef APP-NVUE
+				const ref = this.$refs['u-tabs__wrapper__nav__line']
+				animation.transition(ref, {
+					styles: {
+						transform: `translateX(${x}px)`
+					},
+					duration
+				})
+				// #endif
+			},
+			// 鐐瑰嚮鏌愪竴涓爣绛�
+			clickHandler(item, index) {
+				// 鍥犱负鏍囩鍙兘涓篸isabled鐘舵�侊紝鎵�浠lick鏄竴瀹氫細鍙戝嚭鐨勶紝浣嗘槸change浜嬩欢鏄渶瑕佸彲鐢ㄧ殑鐘舵�佹墠鍙戝嚭
+				this.$emit('click', {
+					...item,
+					index
+				})
+				// 濡傛灉disabled鐘舵�侊紝杩斿洖
+				if (item.disabled) return
+				this.innerCurrent = index
+				this.resize()
+				this.$emit('change', {
+					...item,
+					index
+				})
+			},
+			init() {
+				uni.$u.sleep().then(() => {
+					this.resize()
+				})
+			},
+			setScrollLeft() {
+				// 褰撳墠娲诲姩tab鐨勫竷灞�淇℃伅锛屾湁tab鑿滃崟鐨剋idth鍜宭eft(涓哄厓绱犲乏杈圭晫鍒扮埗鍏冪礌宸﹁竟鐣岀殑璺濈)绛変俊鎭�
+				const tabRect = this.list[this.innerCurrent]
+				// 绱姞寰楀埌褰撳墠item鍒板乏杈圭殑璺濈
+				const offsetLeft = this.list
+					.slice(0, this.innerCurrent)
+					.reduce((total, curr) => {
+						return total + curr.rect.width
+					}, 0)
+				// 姝ゅ涓哄睆骞曞搴�
+				const windowWidth = uni.$u.sys().windowWidth
+				// 灏嗘椿鍔ㄧ殑tabs-item绉诲姩鍒板睆骞曟涓棿锛屽疄闄呬笂鏄scroll-view鐨勭Щ鍔�
+				let scrollLeft = offsetLeft - (this.tabsRect.width - tabRect.rect.width) / 2 - (windowWidth - this.tabsRect
+					.right) / 2 + this.tabsRect.left / 2
+				// 杩欓噷鍋氫竴涓檺鍒讹紝闄愬埗scrollLeft鐨勬渶澶у�间负鏁翠釜scroll-view瀹藉害鍑忓幓tabs缁勪欢鐨勫搴�
+				scrollLeft = Math.min(scrollLeft, this.scrollViewWidth - this.tabsRect.width)
+				this.scrollLeft = Math.max(0, scrollLeft)
+			},
+			// 鑾峰彇鎵�鏈夋爣绛剧殑灏哄
+			resize() {
+				// 濡傛灉涓嶅瓨鍦╨ist锛屽垯涓嶅鐞�
+				if(this.list.length === 0) {
+					return
+				}
+				Promise.all([this.getTabsRect(), this.getAllItemRect()]).then(([tabsRect, itemRect = []]) => {
+					this.tabsRect = tabsRect
+					this.scrollViewWidth = 0
+					itemRect.map((item, index) => {
+						// 璁$畻scroll-view鐨勫搴︼紝杩欓噷
+						this.scrollViewWidth += item.width
+						// 鍙﹀璁$畻姣忎竴涓猧tem鐨勪腑蹇冪偣X杞村潗鏍�
+						this.list[index].rect = item
+					})
+					// 鑾峰彇浜唗abs鐨勫昂瀵镐箣鍚庯紝璁剧疆婊戝潡鐨勪綅缃�
+					this.setLineLeft()
+					this.setScrollLeft()
+				})
+			},
+			// 鑾峰彇瀵艰埅鑿滃崟鐨勫昂瀵�
+			getTabsRect() {
+				return new Promise(resolve => {
+					this.queryRect('u-tabs__wrapper__scroll-view').then(size => resolve(size))
+				})
+			},
+			// 鑾峰彇鎵�鏈夋爣绛剧殑灏哄
+			getAllItemRect() {
+				return new Promise(resolve => {
+					const promiseAllArr = this.list.map((item, index) => this.queryRect(
+						`u-tabs__wrapper__nav__item-${index}`, true))
+					Promise.all(promiseAllArr).then(sizes => resolve(sizes))
+				})
+			},
+			// 鑾峰彇鍚勪釜鏍囩鐨勫昂瀵�
+			queryRect(el, item) {
+				// #ifndef APP-NVUE
+				// $uGetRect涓簎View鑷甫鐨勮妭鐐规煡璇㈢畝鍖栨柟娉曪紝璇﹁鏂囨。浠嬬粛锛歨ttps://www.uviewui.com/js/getRect.html
+				// 缁勪欢鍐呴儴涓�鑸敤this.$uGetRect锛屽澶栫殑涓簎ni.$u.getRect锛屼簩鑰呭姛鑳戒竴鑷达紝鍚嶇О涓嶅悓
+				return new Promise(resolve => {
+					this.$uGetRect(`.${el}`).then(size => {
+						resolve(size)
+					})
+				})
+				// #endif
+
+				// #ifdef APP-NVUE
+				// nvue涓嬶紝浣跨敤dom妯″潡鏌ヨ鍏冪礌楂樺害
+				// 杩斿洖涓�涓猵romise锛岃璋冪敤姝ゆ柟娉曠殑涓讳綋鑳戒娇鐢╰hen鍥炶皟
+				return new Promise(resolve => {
+					dom.getComponentRect(item ? this.$refs[el][0] : this.$refs[el], res => {
+						resolve(res.size)
+					})
+				})
+				// #endif
+			},
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-tabs {
+
+		&__wrapper {
+			@include flex;
+			align-items: center;
+
+			&__scroll-view-wrapper {
+				flex: 1;
+				/* #ifndef APP-NVUE */
+				overflow: auto hidden;
+				/* #endif */
+			}
+
+			&__scroll-view {
+				@include flex;
+				flex: 1;
+			}
+
+			&__nav {
+				@include flex;
+				position: relative;
+
+				&__item {
+					padding: 0 11px;
+					@include flex;
+					align-items: center;
+					justify-content: center;
+
+					&--disabled {
+						/* #ifndef APP-NVUE */
+						cursor: not-allowed;
+						/* #endif */
+					}
+
+					&__text {
+						font-size: 15px;
+						color: $u-content-color;
+
+						&--disabled {
+							color: $u-disabled-color !important;
+						}
+					}
+				}
+
+				&__line {
+					height: 3px;
+					background: $u-primary;
+					width: 30px;
+					position: absolute;
+					bottom: 2px;
+					border-radius: 100px;
+					transition-property: transform;
+					transition-duration: 300ms;
+				}
+			}
+		}
+	}
+</style>

--
Gitblit v1.9.3