Explorar o código

refactor:分类页

Tim_Walker hai 1 ano
pai
achega
ca3471983a

+ 0 - 121
src/components/classfyBox/classfyBox.vue

@@ -1,121 +0,0 @@
-<template>
-	<view class="flex">
-		<view class="type">
-			<view class="item" :style="{'background-color':index===typeCurrent?'#fff':'',color:index===typeCurrent?'rgb(89,146,187)':''}" v-for="(item,index) in typeArr" :key="item.code" @tap="chooseType(item.name,item.code,index)">{{item.name}}</view>
-		</view>
-		<view class="detialType">
-			<view style="padding-left: 15rpx;">{{typeName}}</view>
-			<view class="content">
-				<span v-for="item in detialTypeArr" :key="item.id">{{item.type.description}}</span>
-			</view>
-		</view>
-	</view>
-</template>
-
-<script>
-import { mapState } from 'vuex';
-	export default {
-		name:"classfyBox",
-		data() {
-			return {
-				typeCode:'',
-				typeName:'',
-				typeCurrent: 0,
-				typeArr:[
-					{
-						name:'汽车美容',
-						code:'BEAUTY'
-					},
-					{
-						name:'汽车维修',
-						code:'REPAIR'
-					},
-					{
-						name:'汽车保养',
-						code:'MAINTAIN'
-					},
-					{
-						name:'租车服务',
-						code:'LEASE'
-					},
-					{
-						name:'买车卖车',
-						code:'TRADE'
-					},
-					{
-						name:'汽车改装',
-						code:'REFIT'
-					},
-					{
-						name:'其他服务',
-						code:'OTHER'
-					}
-				],
-			};
-		},
-		mounted(){
-			this.init()
-		},
-		computed:{
-			...mapState(['data']),
-			detialTypeArr(){
-				return this.data.categories.filter(i=>i.type.code === this.typeCode)
-			}
-		},
-		methods:{
-			init(){
-				this.typeCode = this.typeArr[0].code
-				this.typeName = this.typeArr[0].name
-			},
-			chooseType(name,code,index){
-				this.typeName = name
-				this.typeCode = code
-				this.typeCurrent = index
-			}
-		},
-	}
-</script>
-
-<style lang="scss">
-.flex{
-	display: flex;
-	flex-wrap: nowrap;
-	height: 100vh;
-}
-.type{
-	height: 100%;
-	width: 200rpx;
-	background-color: rgb(225,225,225);
-	flex: none;
-	.item{
-		height: 50px;
-		line-height: 50px;
-		text-align: center;
-		width: 100%;
-	}
-}
-.detialType{
-	flex: auto;
-	height:100%;
-	padding: 10rpx;
-	box-sizing: border-box;
-	background-color: rgb(239,239,239);
-}
-.content{
-	display: grid;
-	grid-template-columns:repeat(4,125rpx);
-	grid-column-gap: 10rpx;
-	grid-row-gap: 30rpx;
-	justify-content: center;
-	align-content: center;
-	padding-top: 20rpx;
-	span{
-		background-color: rgb(204,204,204);
-		font-size: 24rpx;
-		height: 60rpx;
-		line-height: 60rpx;
-		text-align: center;
-		overflow: hidden;
-	}
-}
-</style>

+ 337 - 11
src/pages/business/classfication/classfication.vue

@@ -1,19 +1,345 @@
 <template>
-	<view>
-		<classfyBox></classfyBox>
-	</view>
+  <view class="u-wrap">
+    <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 cateList"
+          :key="index"
+          class="u-tab-item"
+          :class="[current == index ? 'u-tab-item-active' : '']"
+          @tap.stop="handleLeftMenuClick(index)"
+        >
+          <text class="u-line-1">{{ item.description }}</text>
+        </view>
+      </scroll-view>
+      <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 cateList"
+            :key="index"
+          >
+            <view class="item-title">
+              <text>{{ item.description }}</text>
+            </view>
+            <view class="item-container">
+              <view
+                class="thumb-box"
+                v-for="(child, childIndex) in item.children"
+                :key="childIndex"
+                @click="featureC(child.id, child.name)"
+              >
+                <image
+                  v-if="child.icon != ''"
+                  class="item-menu-image"
+                  :src="child.icon"
+                  mode=""
+                ></image>
+                <view
+                  v-else
+                  class="item-menu-image row-c"
+                  style="background-color: #f4f6f8"
+                  ><text style="font-size: 20rpx; color: #d0d0d0"
+                    >加载失败</text
+                  ></view
+                >
+                <view class="item-menu-name">{{ child.name }}</view>
+              </view>
+            </view>
+          </view>
+        </view>
+      </scroll-view>
+    </view>
+  </view>
 </template>
 
 <script>
-	export default {
-		data() {
-			return {
-				
-			};
-		}
-	}
+import { buildTree } from '@/utils/tools.js';
+export default {
+  data() {
+    return {
+      scrollTop: 0, //tab标题的滚动条位置
+      oldScrollTop: 0, // tab标题的滚动条旧位置
+      current: 0, // 预设当前项的值
+      menuHeight: 0, // 左边菜单的高度
+      menuItemHeight: 0, // 左边菜单item的高度
+      itemId: '', // 栏目右边scroll-view用于滚动的id
+      cateList: [], // 渲染的数据
+      arr: [], // 储存距离顶部高度的数组
+      scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
+      timer: null, // 定时器
+    };
+  },
+  onLoad() {
+    this.cateList = buildTree(this.getCache('categories'));
+  },
+  onReady() {
+    this.getMenuItemTop();
+  },
+  methods: {
+    /**
+     * 获取一个目标元素的高度
+     * @elClass 元素的类名
+     * @dataVal 储存高度的对象
+     */
+    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();
+            },
+          )
+          .exec();
+      });
+    },
+    /**
+     * 获取右边菜单每个item到顶部的距离
+     * 储存到 arr 数组里面用于后面滚动判断
+     */
+    getMenuItemTop() {
+      new Promise(resolve => {
+        let selectorQuery = uni.createSelectorQuery();
+        selectorQuery
+          .selectAll('.class-item')
+          .boundingClientRect(rects => {
+            // 如果节点尚未生成,rects值为[](因为用selectAll,所以返回的是数组),循环调用执行
+            if (!rects.length) {
+              setTimeout(() => {
+                this.getMenuItemTop();
+              }, 10);
+              return;
+            }
+            rects.forEach(rect => {
+              // 视情况而定,这里减去rects[0].top,是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
+              // this.arr.push(rect.top - rects[0].top);
+              this.arr.push(rect.top);
+              resolve();
+            });
+          })
+          .exec();
+      });
+    },
+    /**
+     * 观测元素相交状态
+     * 检测右边scroll-view的id为itemxx的元素与right-box的相交状态
+     * 如果跟.right-box底部相交,就动态设置左边栏目的活动状态
+     */
+    async observer() {
+      this.cateList.map((val, index) => {
+        let observer = uni.createIntersectionObserver(this);
+        observer
+          .relativeTo('.right-box', {
+            top: 0,
+          })
+          .observe('#item' + index, res => {
+            if (res.intersectionRatio > 0) {
+              let id = res.id.substring(4);
+              this.leftMenuStatus(id);
+            }
+          });
+      });
+    },
+    /**
+     * 设置左边菜单的滚动状态
+     * @index 传入的 ID
+     */
+    async leftMenuStatus(index) {
+      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;
+    },
+    /**
+     * 点击左边的栏目切换
+     * @index 传入的 ID
+     */
+    async handleLeftMenuClick(index) {
+      if (this.arr.length == 0) {
+        await this.getMenuItemTop();
+      }
+      if (index == this.current) return;
+      this.scrollRightTop = this.oldScrollTop;
+      this.$nextTick(function () {
+        this.scrollRightTop = this.arr[index];
+        this.current = index;
+        this.leftMenuStatus(index);
+      });
+    },
+    /**
+     * 右边菜单滚动
+     * 如果不存在height2,意味着数据循环已经到了最后一个,设置左边菜单为最后一项即可
+     */
+    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');
+      }
+      setTimeout(() => {
+        // 节流
+        this.timer = null;
+        // scrollHeight为右边菜单垂直中点位置
+        // let scrollHeight = e.detail.scrollTop + this.menuHeight / 2;
+        // scrollHeight为右边菜单头部位置
+        let scrollHeight = e.detail.scrollTop + 20;
+        for (let i = 0; i < this.arr.length; i++) {
+          let height1 = this.arr[i];
+          let height2 = this.arr[i + 1];
+          if (!height2 || (scrollHeight >= height1 && scrollHeight < height2)) {
+            this.leftMenuStatus(i);
+            return;
+          }
+        }
+      }, 10);
+    },
+  },
+};
 </script>
 
-<style lang="scss">
+<style scoped>
+.u-wrap {
+  /* #ifdef H5 */
+  height: calc(100vh - var(--window-top));
+  /* #endif */
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+}
 
+.u-search-box {
+  padding: 18rpx 30rpx;
+}
+
+.u-menu-wrap {
+  flex: 1;
+  display: flex;
+  overflow: hidden;
+}
+
+.u-tab-view {
+  width: 200rpx;
+  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-active {
+  position: relative;
+  color: #06a446;
+  font-size: 30rpx;
+  font-weight: 500;
+  background: #d6ffe7;
+}
+
+.u-tab-item-active::before {
+  content: '';
+  position: absolute;
+  border-left: 4px solid #06a446;
+  height: 52rpx;
+  left: 0;
+  top: 29rpx;
+}
+
+.u-tab-view {
+  height: 100%;
+}
+
+.right-box {
+  background-color: rgb(250, 250, 250);
+}
+
+.page-view {
+  padding: 16rpx;
+}
+
+.class-item {
+  margin-bottom: 30rpx;
+  background-color: #fff;
+  padding: 16rpx;
+  border-radius: 8rpx;
+}
+
+.class-item:last-child {
+  min-height: 100vh;
+}
+
+.item-title {
+  font-size: 26rpx;
+  color: #06a446;
+  font-weight: bold;
+}
+
+.item-menu-name {
+  margin-top: 8rpx;
+  font-weight: normal;
+  font-size: 24rpx;
+  color: #06a446;
+}
+
+.item-container {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.thumb-box {
+  width: 33.333333%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  margin-top: 20rpx;
+}
+
+.item-menu-image {
+  width: 120rpx;
+  height: 120rpx;
+}
 </style>

+ 16 - 0
src/utils/tools.js

@@ -0,0 +1,16 @@
+export function buildTree(data) {
+  const tree = {};
+
+  data.forEach(item => {
+    const code = item.type.code;
+    const node = { icon: item.icon, id: item.id, name: item.name };
+
+    if (!tree[code]) {
+      tree[code] = { code: code, description: item.type.description, children: [node] };
+    } else {
+      tree[code].children.push(node);
+    }
+  });
+
+  return Object.values(tree);
+}