Просмотр исходного кода

feat: 工作台增加导航栏

liujt 2 месяцев назад
Родитель
Сommit
0fb0cf4f5a
2 измененных файлов с 291 добавлено и 179 удалено
  1. 48 0
      pages/index/index.scss
  2. 243 179
      pages/index/index.vue

+ 48 - 0
pages/index/index.scss

@@ -3,6 +3,54 @@ page{
 	color: $uni-text-color;
 }
 
+/* 顶部横向导航 */
+.top-nav-wrapper {
+	position: fixed;
+	top: calc(var(--status-bar-height) + 44px);
+	left: 0;
+	right: 0;
+	z-index: 999;
+	background-color: #fff;
+	border-bottom: 1rpx solid #eee;
+}
+.top-nav {
+	width: 100%;
+	white-space: nowrap;
+	padding: 0 20rpx;
+	height: 80rpx;
+	box-sizing: border-box;
+}
+.nav-tab {
+	display: inline-block;
+	padding: 0 30rpx;
+	height: 80rpx;
+	line-height: 80rpx;
+	font-size: 28rpx;
+	color: #666;
+	text-align: center;
+	flex-shrink: 0;
+	position: relative;
+}
+.nav-tab.active {
+	color: $j-primary-border-green;
+	font-weight: 600;
+}
+.nav-tab.active::after {
+	content: '';
+	position: absolute;
+	bottom: 0;
+	left: 50%;
+	transform: translateX(-50%);
+	width: 60rpx;
+	height: 4rpx;
+	background-color: $j-primary-border-green;
+	border-radius: 2rpx;
+}
+
+.content-wrapper {
+	padding-top: calc(var(--status-bar-height) + 80rpx);
+}
+
 /* 基本信息样式 */
 	.nav{
 	.nav-content{

+ 243 - 179
pages/index/index.vue

@@ -2,173 +2,148 @@
 	<view>
 		<uni-nav-bar fixed="true" statusBar="true" title="工作台" right-icon="scan"
 			@clickRight="HandlScanCode"></uni-nav-bar>
-		<view v-if="internalManagementList.length > 0">
-			<CellTip title="协同办公"></CellTip>
-			<view class="nav">
-				<view class="nav-content">
-					<view class="nav-item" v-for="(item, index) in internalManagementList" @click="toNav(item.path)">
-						<span :class="'iconfont ' + item.icon"></span>
-						<i class="badge" v-if="item.badge">{{ item.badge }}</i>
-						<label>{{ item.name }}</label>
+		<!-- 顶部横向导航 -->
+		<view class="top-nav-wrapper" v-if="moduleNavList.length > 0">
+			<scroll-view class="top-nav" scroll-x enable-flex :scroll-into-view="scrollIntoViewId" scroll-with-animation @scroll="handleNavScroll">
+				<view class="nav-tab" v-for="(item, index) in moduleNavList" :key="index" :id="'nav-' + item.id" :class="{ active: currentNavIndex === index }" @click="goToModule(item, index)">
+					{{ item.name }}
+				</view>
+			</scroll-view>
+		</view>
+		<view class="content-wrapper">
+			<view v-if="internalManagementList.length > 0" :id="'module-xietong'" :data-index="0">
+				<CellTip title="协同办公"></CellTip>
+				<view class="nav">
+					<view class="nav-content">
+						<view class="nav-item" v-for="(item, index) in internalManagementList" @click="toNav(item.path)">
+							<span :class="'iconfont ' + item.icon"></span>
+							<i class="badge" v-if="item.badge">{{ item.badge }}</i>
+							<label>{{ item.name }}</label>
+						</view>
 					</view>
 				</view>
 			</view>
-		</view>
-		<view v-if="productionList.length > 0">
-			<CellTip title="生产管理"></CellTip>
-			<view class="nav">
-				<view class="nav-content">
-					<view class="nav-item" v-for="item in productionList" :key="item.link_url"
-						@click="toNav(item.path)">
-						<span :class="'iconfont ' + item.icon"></span>
-						<label>{{ item.title }}</label>
-						<label>{{ item.num }}</label>
+			<view v-if="productionList.length > 0" :id="'module-shengchan'" :data-index="1">
+				<CellTip title="生产管理"></CellTip>
+				<view class="nav">
+					<view class="nav-content">
+						<view class="nav-item" v-for="item in productionList" :key="item.link_url"
+							@click="toNav(item.path)">
+							<span :class="'iconfont ' + item.icon"></span>
+							<label>{{ item.title }}</label>
+							<label>{{ item.num }}</label>
+						</view>
 					</view>
 				</view>
 			</view>
-		</view>
-		<!-- <view>
-      <CellTip title="生产执行"></CellTip>
-      <view class="nav">
-        <view class="nav-content">
-          <view
-            class="nav-item"
-            v-for="(item, index) in executeList"
-            @click="toNav(item.link_url)"
-          >
-            <span :class="item.class"></span>
-            <label>{{ item.title }}</label>
-            <label>{{ item.num }}</label>
-          </view>
-        </view>
-      </view>
-    </view> -->
-		<view v-if="operationsList.length > 0">
-			<CellTip title="运维管理"></CellTip>
-			<view class="nav">
-				<view class="nav-content">
-					<view class="nav-item" v-for="(item, index) in operationsList" @click="toNav(item.path)">
-						<span :class="'iconfont ' + item.icon"></span>
-						<i class="badge" v-if="item.badge">{{ item.badge }}</i>
-						<label>{{ item.name }}</label>
+			<view v-if="operationsList.length > 0" :id="'module-yunwei'" :data-index="2">
+				<CellTip title="运维管理"></CellTip>
+				<view class="nav">
+					<view class="nav-content">
+						<view class="nav-item" v-for="(item, index) in operationsList" @click="toNav(item.path)">
+							<span :class="'iconfont ' + item.icon"></span>
+							<i class="badge" v-if="item.badge">{{ item.badge }}</i>
+							<label>{{ item.name }}</label>
+						</view>
 					</view>
 				</view>
 			</view>
-		</view>
-		<view v-if="serviceList.length > 0">
-			<CellTip title="售后服务管理"></CellTip>
-			<view class="nav">
-				<view class="nav-content">
-					<view class="nav-item" v-for="(item, index) in serviceList" @click="toNav(item.path)">
-						<span :class="'iconfont ' + item.icon"></span>
-						<i class="badge" v-if="item.badge">{{ item.badge }}</i>
-						<label>{{ item.name }}</label>
+			<view v-if="serviceList.length > 0" :id="'module-shouhou'" :data-index="3">
+				<CellTip title="售后服务管理"></CellTip>
+				<view class="nav">
+					<view class="nav-content">
+						<view class="nav-item" v-for="(item, index) in serviceList" @click="toNav(item.path)">
+							<span :class="'iconfont ' + item.icon"></span>
+							<i class="badge" v-if="item.badge">{{ item.badge }}</i>
+							<label>{{ item.name }}</label>
+						</view>
 					</view>
 				</view>
 			</view>
-		</view>
-		<view v-if="warehousingList.length > 0">
-			<CellTip title="仓储管理"></CellTip>
-
-			<view class="nav">
-				<view class="nav-content">
-					<view class="nav-item" v-for="(item, index) in warehousingList" @click="toNav(item.path)">
-						<span :class="'iconfont ' + item.icon"></span>
-						<label>{{ item.title }}</label>
-						<!-- <label>{{ item.num }}</label> -->
+			<view v-if="warehousingList.length > 0" :id="'module-cangchu'" :data-index="4">
+				<CellTip title="仓储管理"></CellTip>
+				<view class="nav">
+					<view class="nav-content">
+						<view class="nav-item" v-for="(item, index) in warehousingList" @click="toNav(item.path)">
+							<span :class="'iconfont ' + item.icon"></span>
+							<label>{{ item.title }}</label>
+						</view>
 					</view>
 				</view>
 			</view>
-		</view>
-		<view v-if="dispatchList.length > 0">
-			<CellTip title="运输管理"></CellTip>
-			<view class="nav">
-				<view class="nav-content">
-					<view class="nav-item" v-for="(item, index) in dispatchList" @click="toNav(item.path)">
-						<span :class="'iconfont ' + item.icon"></span>
-						<i class="badge" v-if="item.badge">{{ item.badge }}</i>
-						<label>{{ item.name }}</label>
+			<view v-if="dispatchList.length > 0" :id="'module-yunshu'" :data-index="5">
+				<CellTip title="运输管理"></CellTip>
+				<view class="nav">
+					<view class="nav-content">
+						<view class="nav-item" v-for="(item, index) in dispatchList" @click="toNav(item.path)">
+							<span :class="'iconfont ' + item.icon"></span>
+							<i class="badge" v-if="item.badge">{{ item.badge }}</i>
+							<label>{{ item.name }}</label>
+						</view>
 					</view>
 				</view>
 			</view>
-		</view>
-		<view v-if="saleManageList.length > 0">
-			<CellTip title="营销管理"></CellTip>
-			<view class="nav">
-				<view class="nav-content">
-					<view class="nav-item" v-for="(item, index) in saleManageList" @click="toNav(item.path)">
-						<span :class="'iconfont ' + item.icon"></span>
-						<i class="badge" v-if="item.badge">{{ item.badge }}</i>
-						<label>{{ item.name }}</label>
+			<view v-if="saleManageList.length > 0" :id="'module-yingxiao'" :data-index="6">
+				<CellTip title="营销管理"></CellTip>
+				<view class="nav">
+					<view class="nav-content">
+						<view class="nav-item" v-for="(item, index) in saleManageList" @click="toNav(item.path)">
+							<span :class="'iconfont ' + item.icon"></span>
+							<i class="badge" v-if="item.badge">{{ item.badge }}</i>
+							<label>{{ item.name }}</label>
+						</view>
 					</view>
 				</view>
 			</view>
-		</view>
-		<view v-if="purchaseManageList.length > 0">
-			<CellTip title="采购管理"></CellTip>
-			<view class="nav">
-				<view class="nav-content">
-					<view class="nav-item" v-for="(item, index) in purchaseManageList" @click="toNav(item.path)">
-						<span :class="'iconfont ' + item.icon"></span>
-						<i class="badge" v-if="item.badge">{{ item.badge }}</i>
-						<label>{{ item.name }}</label>
+			<view v-if="purchaseManageList.length > 0" :id="'module-caigou'" :data-index="7">
+				<CellTip title="采购管理"></CellTip>
+				<view class="nav">
+					<view class="nav-content">
+						<view class="nav-item" v-for="(item, index) in purchaseManageList" @click="toNav(item.path)">
+							<span :class="'iconfont ' + item.icon"></span>
+							<i class="badge" v-if="item.badge">{{ item.badge }}</i>
+							<label>{{ item.name }}</label>
+						</view>
 					</view>
 				</view>
 			</view>
-		</view>
-		<view v-if="pcsList.length > 0">
-			<CellTip title="生产管控"></CellTip>
-			<view class="nav">
-				<view class="nav-content">
-					<view class="nav-item" v-for="(item, index) in pcsList" @click="toNav(item.path)">
-						<span :class="'iconfont ' + item.icon"></span>
-						<i class="badge" v-if="item.badge">{{ item.badge }}</i>
-						<label>{{ item.name }}</label>
+			<view v-if="pcsList.length > 0" :id="'module-shengchanguanli'" :data-index="8">
+				<CellTip title="生产管控"></CellTip>
+				<view class="nav">
+					<view class="nav-content">
+						<view class="nav-item" v-for="(item, index) in pcsList" @click="toNav(item.path)">
+							<span :class="'iconfont ' + item.icon"></span>
+							<i class="badge" v-if="item.badge">{{ item.badge }}</i>
+							<label>{{ item.name }}</label>
+						</view>
 					</view>
 				</view>
 			</view>
-		</view>
-		<view v-if="qmsList.length > 0">
-			<CellTip title="质量管理"></CellTip>
-			<view class="nav">
-				<view class="nav-content">
-					<view class="nav-item" v-for="(item, index) in qmsList" @click="toNav(item.path)">
-						<span :class="'iconfont ' + item.icon"></span>
-						<i class="badge" v-if="item.badge">{{ item.badge }}</i>
-						<label>{{ item.name }}</label>
+			<view v-if="qmsList.length > 0" :id="'module-zhiliang'" :data-index="9">
+				<CellTip title="质量管理"></CellTip>
+				<view class="nav">
+					<view class="nav-content">
+						<view class="nav-item" v-for="(item, index) in qmsList" @click="toNav(item.path)">
+							<span :class="'iconfont ' + item.icon"></span>
+							<i class="badge" v-if="item.badge">{{ item.badge }}</i>
+							<label>{{ item.name }}</label>
+						</view>
 					</view>
 				</view>
 			</view>
-		</view>
-		<view v-if="traceabilityList.length > 0">
-			<CellTip title="溯源管理"></CellTip>
-			<view class="nav">
-				<view class="nav-content">
-					<view class="nav-item" v-for="(item, index) in traceabilityList" @click="toNav(item.path)">
-						<span :class="'iconfont ' + item.icon"></span>
-						<i class="badge" v-if="item.badge">{{ item.badge }}</i>
-						<label>{{ item.name }}</label>
+			<view v-if="traceabilityList.length > 0" :id="'module-suyuan'" :data-index="10">
+				<CellTip title="溯源管理"></CellTip>
+				<view class="nav">
+					<view class="nav-content">
+						<view class="nav-item" v-for="(item, index) in traceabilityList" @click="toNav(item.path)">
+							<span :class="'iconfont ' + item.icon"></span>
+							<i class="badge" v-if="item.badge">{{ item.badge }}</i>
+							<label>{{ item.name }}</label>
+						</view>
 					</view>
 				</view>
 			</view>
 		</view>
-		<!--    <view>
-      <CellTip title="生产类"></CellTip>
-
-      <view class="nav">
-        <view class="nav-content">
-          <view
-            class="nav-item"
-            v-for="(item, index) in productionList"
-            @click="toNav(item.link_url)"
-          >
-            <span :class="item.class"></span>
-            <label>{{ item.title }}</label>
-            <label>{{ item.num }}</label>
-          </view>
-        </view>
-      </view>
-    </view> -->
 	</view>
 </template>
 
@@ -190,42 +165,8 @@
 				qmsList: [],
 				//生产类
 				productionList: [],
-				// executeList: [
-				//   {
-				//     class: "iconfont icon-gongdanguanli",
-				//     title: "成型",
-				//     link_url: "/pages/production/execute/extrusion/index",
-				//     // "num": 1
-				//   },
-				//   {
-				//     class: "iconfont icon-gongdanguanli",
-				//     title: "自然干燥",
-				//     link_url: "/pages/production/execute/drying/index",
-				//     // "num": 1
-				//   },
-				//   {
-				//     class: "iconfont icon-gongdanguanli",
-				//     title: "升温干燥",
-				//     link_url: "/pages/production/execute/heating/index",
-				//     // "num": 1
-				//   },
-				//   {
-				//     class: "iconfont icon-gongdanguanli",
-				//     title: "半加定长",
-				//     link_url: "/pages/production/execute/halfAdded/index",
-				//     // "num": 1
-				//   },
-				//   // {
-				//   //   class: "iconfont icon-gongdanguanli",
-				//   //   title: "领料",
-				//   //   link_url: "/pages/production/execute/picking/index",
-				//   //   // "num": 1
-				//   // },
-				// ],
 				//仓储管理
-
 				warehousingList: [],
-
 				//运维类
 				operationsList: [],
 				dispatchList: [],
@@ -238,7 +179,14 @@
 				traceabilityList: [],
 				// 采购管理
 				purchaseManageList: [],
-				pcsList: []
+				pcsList: [],
+				// 导航相关
+				moduleNavList: [],
+				scrollIntoViewId: '',
+				currentNavIndex: 0,
+				moduleObserver: null,
+				// 顶部固定区域高度(状态栏 + nav-bar + 横向导航)
+				topNavHeight: 0
 			};
 		},
 		created() {
@@ -246,6 +194,17 @@
 		},
 		onShow() {
 			this.getStatistics();
+			this.$nextTick(() => {
+				this.calculateTopNavHeight();
+			});
+		},
+		onReady() {
+			// 页面准备完成后,滚动到第一个模块
+			this.$nextTick(() => {
+				if (this.moduleNavList.length > 0) {
+					this.goToModule(this.moduleNavList[0], 0);
+				}
+			});
 		},
 		onHide() {
 			clearTimeout(this.timer);
@@ -254,13 +213,20 @@
 			clearTimeout(this.timer);
 		},
 		methods: {
+			// 计算顶部固定区域高度(动态获取实际高度)
+			calculateTopNavHeight() {
+				const query = uni.createSelectorQuery().in(this);
+				query.select('.top-nav-wrapper').boundingClientRect((rect) => {
+					if (rect) {
+						this.topNavHeight = rect.bottom;
+					}
+				}).exec();
+			},
 			getStatistics() {
 				// 获取工单统计数
 				statistics()
 					.then((res) => {
 						this.workOrder = res;
-						// console.log("this.operationsList---", this.operationsList);
-						// console.log("res", res);
 						this.operationsList.forEach((item) => {
 							if (item.name == "保养工单") item.badge = res.maintenanceNum;
 							if (item.name == "巡点检工单") item.badge = res.patrolInspection;
@@ -278,16 +244,18 @@
 				let list = JSON.parse(_list) || [];
 				console.log("-----------list--------------");
 				console.log(list);
+				// 收集所有异步请求的 Promise
+				const asyncPromises = [];
+				
 				if (list[0] && list[0].children.length > 0) {
 					// console.log(list[0].children, "list[0].children ----");
 					list[0].children.forEach((f) => {
 						if (f.path == "productionManage") {
 							this.productionList = f.children;
 						} else if (f.path == "operationsList") {
-							statistics().then((res) => {
+							const promise = statistics().then((res) => {
+								console.log('statistics~~~', res)
 								this.workOrder = res;
-								// console.log("this.operationsList---", this.operationsList);
-								// console.log("res", res);
 								f.children.forEach((item) => {
 									if (item.name == "保养工单") item.badge = res.maintenanceNum;
 									if (item.name == "巡点检工单")
@@ -296,6 +264,7 @@
 								});
 								this.operationsList = f.children;
 							});
+							asyncPromises.push(promise);
 						} else if (f.path == "warehouseManagement") {
 							this.warehousingList = f.children;
 						} else if (f.path == "saleManageList") {
@@ -314,20 +283,16 @@
 						} else if (f.path == "purchasingManage") {
 							this.purchaseManageList = f.children;
 						} else if (f.path == "qualityManage") {
-
-							qualityTodoByPda().then((res) => {
-
-								// console.log("this.operationsList---", this.operationsList);
-								// console.log("res", res);
+							const promise = qualityTodoByPda().then((res) => {
 								f.children.forEach((item) => {
 									if (item.name == "质检工单") item.badge = res.workOrderNum;
 									if (item.name == "我的质检工单") item.badge = res.myWorkOrderNum;
-
 									if (item.name == "我的任务单") item.badge = res.myTaskMonadNum;
 									if (item.name == "我的受托单") item.badge = res.myRequestEntrustNum;
 								});
 								this.qmsList = f.children;
 							});
+							asyncPromises.push(promise);
 						} else if (f.path == "productionControlManagement") {
 							this.pcsList = f.children;
 						} else if (f.path == 'dispatchManage') {
@@ -335,6 +300,105 @@
 						}
 					});
 				}
+				
+				// 等待所有异步数据加载完成后再生成导航
+				if (asyncPromises.length > 0) {
+					Promise.all(asyncPromises).then(() => {
+						this.buildNavList();
+					});
+				} else {
+					this.buildNavList();
+				}
+			},
+			// 构建导航列表
+			buildNavList() {
+				const navMap = {
+					'internalManagementList': { name: '协同办公', id: 'xietong' },
+					'productionList': { name: '生产管理', id: 'shengchan' },
+					'operationsList': { name: '运维管理', id: 'yunwei' },
+					'serviceList': { name: '售后服务', id: 'shouhou' },
+					'warehousingList': { name: '仓储管理', id: 'cangchu' },
+					'dispatchList': { name: '运输管理', id: 'yunshu' },
+					'saleManageList': { name: '营销管理', id: 'yingxiao' },
+					'purchaseManageList': { name: '采购管理', id: 'caigou' },
+					'pcsList': { name: '生产管控', id: 'shengchanguanli' },
+					'qmsList': { name: '质量管理', id: 'zhiliang' },
+					'traceabilityList': { name: '溯源管理', id: 'suyuan' }
+				};
+		
+				this.moduleNavList = Object.entries(navMap)
+					.filter(([key]) => this[key]?.length > 0)
+					.map(([, value]) => value);
+				this.initObserver();
+			},
+			// 初始化 IntersectionObserver
+			initObserver() {
+				if (this.moduleObserver) {
+					this.moduleObserver.disconnect();
+				}
+				this.$nextTick(() => {
+					const query = uni.createSelectorQuery().in(this);
+					query.select('.top-nav-wrapper').boundingClientRect();
+					query.select('.content-wrapper').boundingClientRect();
+					query.exec((rects) => {
+						if (rects[0]) {
+							this.topNavHeight = rects[0].bottom;
+						}
+						// 初始化 observer
+						this.moduleObserver = uni.createIntersectionObserver(this, {
+							thresholds: [0, 0.5, 1],
+							observeAll: true
+						});
+						// 监测区域顶部设为导航栏底部
+						this.moduleObserver.relativeTo('.content-wrapper', { 
+							top: rects[0] ? rects[0].height : 80 
+						});
+						// 根据 moduleNavList 动态监测模块
+						this.moduleNavList.forEach(item => {
+							this.moduleObserver.observe('#module-' + item.id, (res) => {
+								if (res.intersectionRatio > 0) {
+									const index = parseInt(res.dataset.index);
+									if (!isNaN(index)) {
+										this.currentNavIndex = index;
+									}
+								}
+							});
+						});
+					});
+				});
+			},
+			// 点击导航跳转
+			goToModule(item, index) {
+				this.currentNavIndex = index;
+				this.scrollIntoViewId = 'nav-' + item.id;
+				setTimeout(() => {
+					this.scrollIntoViewId = '';
+					const query = uni.createSelectorQuery().in(this);
+					query.select('#module-' + item.id).boundingClientRect();
+					query.select('.content-wrapper').boundingClientRect();
+					query.exec((res) => {
+						// res[0] 是目标模块,res[1] 是 content-wrapper
+						if (res[0] && res[1]) {
+							const targetTop = res[0].top; // 目标元素到屏幕顶部的距离
+							const contentPaddingTop = res[1].top; // content-wrapper 的 padding-top
+							console.log('targetTop~~~', targetTop, 'contentPaddingTop~~~', contentPaddingTop);
+							uni.pageScrollTo({
+								scrollTop: targetTop - contentPaddingTop - 46,
+								duration: 300
+							});
+						}
+					});
+				}, 200);
+			},
+			// 监听导航滚动
+			handleNavScroll(e) {
+				// 可以在这里添加滚动时的逻辑
+			},
+			onUnload() {
+				if (this.moduleObserver) {
+					this.moduleObserver.disconnect();
+				}
+				clearTimeout(this.timer);
 			},
 
 			toNav(url) {