shish 11 mēneši atpakaļ
vecāks
revīzija
73e6f98129
1 mainītis faili ar 627 papildinājumiem un 3 dzēšanām
  1. 627 3
      mallApp/src/pages/home/mall.vue

+ 627 - 3
mallApp/src/pages/home/mall.vue

@@ -1,5 +1,629 @@
 <template>
-  <view class="app_content">
-    <view></view>
+  <view class="app-main app-content">
+
+		<view class="c_header" :class="myClass">
+			<view class="header_inside">
+				<image :src="shopImg"></image>
+				<view class="shop_info">
+					<view class="shop_title">{{shopName}}</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 搜索框和花材绿植按钮区域 -->
+		<view class="search-wrap">
+			<view class="search-bar">
+				<app-search-module 
+					v-model="searchText" 
+					placeholder="搜索商品" 
+					@input="searchFn" 
+					:height="66"
+					:fontSize="28"
+				/>
+			</view>
+			<view class="buy-material-container">
+				<button class="buy-material-btn" @click="buyProduct()">
+					<view class="btn-content">
+						<text class="btn-text">商城</text>
+					</view>
+				</button>
+			</view>
+		</view>
+
+    <scroll-view scroll-y scroll-with-animation class="tab-view" :scroll-top="scrollTop">
+      <view v-for="(item,index) in tabbar" :key="index" class="tab-bar-item" :class="[!isSearchMode && currentTab==index ? 'active' : '']" :data-current="index" @tap.stop="swichNav($event ,item)" >
+        <text>{{item.categoryName}}</text>
+      </view>
+      <view style="height:150upx"></view>
+    </scroll-view>
+    
+    <!-- 搜索结果展示区域 -->
+    <scroll-view v-if="isSearchMode" scroll-y class="right-box" @scrolltolower="reachBottom">
+      <view class="page-view" v-if="!$util.isEmpty(list.data)" >
+        <view class="goods-list" v-for="(items) in list.data" :key="items.id" @click="toBuy(items)" >
+          <view class="img-blo">
+            <image :src="items.smallCover" mode="aspectFill" ></image>
+          </view>
+          <view class="goods-det">
+            <view class="goods-det-top">
+              <view class="goods-name">{{ items.name }}</view>
+              <view class="goods-sales"></view>
+            </view>
+            <view class="goods-price">¥{{ items.price?parseFloat(items.price):0 }}</view>
+            <view class="buy-btn">购买</view>
+          </view>
+        </view>
+      </view>
+      <block v-else>
+        <app-wrapper-empty 
+          :title="getEmptyTitle()" 
+          :is-empty="true" 
+        />
+      </block>
+    </scroll-view>
+
+    <!-- 分类商品展示区域 -->
+    <block v-for="(item,index) in tabbar" :key="index" >
+      <scroll-view scroll-y class="right-box" @scrolltolower="reachBottom" v-if="!isSearchMode && currentTab==index" >
+        <view class="page-view" v-if="!$util.isEmpty(list.data)" >
+          <view class="goods-list" v-for="(items) in list.data" :key="items.id" @click="toBuy(items)" >
+            <view class="img-blo">
+              <image :src="items.smallCover" mode="aspectFill" ></image>
+            </view>
+            <view class="goods-det">
+              <view class="goods-det-top">
+                <view class="goods-name">{{ items.name }}</view>
+                <view class="goods-sales"></view>
+              </view>
+              <view class="goods-price">¥{{ items.price?parseFloat(items.price):0 }}</view>
+              <view class="buy-btn">购买</view>
+            </view>
+          </view>
+        </view>
+        <block v-else>
+          <app-wrapper-empty 
+            :title="getEmptyTitle()" 
+            :is-empty="true" 
+          />
+        </block>
+      </scroll-view>
+    </block>
+
+    <wangCg :loginStyle.sync="globalLoginStyle" @goToLogin="loginTo()"></wangCg>
   </view>
-</template>
+</template>
+<script>
+import { mapGetters } from 'vuex'
+import AppImg from '@/components/app-img'
+import wangCg from "@/components/wangCg"
+import AppWrapperEmpty from '@/components/app-wrapper-empty'
+import AppSearchModule from '@/components/item/module/app-search'
+import { getClass, getList } from '@/api/category'
+import { list,share } from '@/mixins'
+import { getInfo } from "@/api/shop"
+export default {
+  name: 'mall',
+  components: {
+    AppWrapperEmpty,
+    AppImg,
+    wangCg,
+    AppSearchModule
+  },
+  mixins: [list, share],
+  data () {
+    return {
+      tabbar: [],
+      height: 0,
+      currentTab: 0,
+      scrollTop: 0,
+      form: {
+        categoryId: ''
+      },
+      currentTabIndex: 1,
+      myClass: 'fadeIn',
+			shopName:'',
+			shopImg:'',
+      hdId:0,
+      searchText: '', // 搜索文本
+      searchTimer: null, // 搜索防抖定时器
+      isSearchMode: false, // 是否为搜索模式
+      lastSearchText: '' // 上次搜索的文本,用于优化
+    }
+  },
+	computed: {
+    ...mapGetters({ loginInfo:"getLoginInfo" })
+	},
+  onShareAppMessage(res) {
+		return {
+			title: '花束花礼',
+			desc: "",
+			path: "pages/home/category?account="+this.option.account,
+		}
+	},
+	onShow(){
+		if(!this.$util.isEmpty(this.loginInfo)){
+			this.globalLoginStyle = 1
+		}else{
+			this.globalLoginStyle = 0
+		}
+  },
+  onUnload() {
+    // 清理搜索定时器
+    if (this.searchTimer) {
+      clearTimeout(this.searchTimer);
+      this.searchTimer = null;
+    }
+  },
+
+	watch: {
+		loginInfo: {
+			deep: true,
+			handler: function(newVal) {
+				if(!this.$util.isEmpty(newVal)){
+					this.globalLoginStyle = 1
+				}else{
+					this.globalLoginStyle = 0
+				}
+			},
+			immediate:true
+		}
+	},
+  methods: {
+    toBuy(item){
+      let account = this.option.account?this.option.account:0
+      this.$util.pageTo({url:"/pages/goods/detail?id="+item.id+'&hdId='+this.hdId+'&categoryId='+this.form.categoryId+'&account='+account})
+    },
+    buyProduct(){
+      let account = this.option.account?this.option.account:0
+      this.$util.pageTo({url:"/pages/home/category?account="+account+'&hdId='+this.hdId+'&id='+this.hdId,type:2})
+    },
+    loginTo() {
+      let account = this.option.account?this.option.account:0
+      this.$util.pageTo({url:"/pages/login/index?needBack=1&account="+account})
+    },
+    init(){
+      this.hdId = this.option.hdId ? this.option.hdId : 0
+      this.classInit()
+			getInfo({hdId:this.hdId}).then(res=>{
+				if(res.code == 1){
+					if(res.data.info && res.data.info.merchantName){
+						let sjName = res.data.info.merchantName
+						let shopName = res.data.info.shopName ? res.data.info.shopName : '首店'
+						let name = shopName == '首店' ? sjName : sjName+' '+shopName
+						this.shopName = name
+						this.shopImg = res.data.info.avatar
+					}
+
+					if(res.data.hd && Number(res.data.hd.id) > 0){
+						if(Number(this.hdId) == 0){
+							this.hdId = res.data.hd.id
+						}
+					}
+
+				}
+			})
+    },
+    // 搜索输入处理(防抖)
+    searchFn() {
+
+      // 清除之前的定时器
+      if (this.searchTimer) {
+        clearTimeout(this.searchTimer)
+        this.searchTimer = null
+      }
+      
+      // 如果搜索文本为空
+      if (this.$util.isEmpty(this.searchText)) {
+        // 如果之前也是空文本(首次点击或焦点清空),跳过搜索
+        if (this.$util.isEmpty(this.lastSearchText)) {
+          return
+        }
+        // 如果之前有搜索文本,现在清空了,则恢复分类筛选
+        this.clearSearch()
+        return
+      }
+      
+      // 如果搜索文本与上次相同,不重复搜索
+      if (this.searchText === this.lastSearchText) {
+        return
+      }
+      
+      // 设置防抖定时器,1秒后执行搜索
+      this.searchTimer = setTimeout(() => {
+        this.performSearch()
+      }, 1000)
+    },
+    
+    // 执行搜索
+    performSearch() {
+      if (this.$util.isEmpty(this.searchText)) {
+        return
+      }
+      
+      this.isSearchMode = true
+      this.lastSearchText = this.searchText
+      this.resetList()
+      this.searchGoods()
+    },
+    
+    // 清空搜索,恢复分类筛选
+    clearSearch() {
+      this.isSearchMode = false
+      this.lastSearchText = ''
+      
+      // 确保搜索框内容被清空
+      this.$nextTick(() => {
+        this.searchText = ''
+      })
+      
+      this.resetList()
+      this.getGoodList()
+    },
+    
+    // 搜索商品
+    searchGoods() {
+      let hdId = this.option.id?this.option.id:0
+      return getList({ 
+        searchText: this.searchText, 
+        page: this.list.page, 
+        pageSize: 10,
+        hdId:hdId
+      }).then(res => {
+        this.completes(res)
+      }).catch(err => {
+        this.$msg('搜索失败')
+      })
+    },
+    reachBottom () {
+      console.log('reachBottom----------', this.list.finished)
+      if (this.list.finished == false) {
+        // 根据是否为搜索模式决定调用哪个方法
+        if (this.isSearchMode) {
+          this.searchGoods().then(res => {
+            uni.stopPullDownRefresh()
+          });
+        } else {
+          this.getGoodList().then(res => {
+            uni.stopPullDownRefresh()
+          });
+        }
+      } else {
+        uni.stopPullDownRefresh()
+      }
+    },
+    async classInit () {
+      this.getMyClass() .then(res => {
+        this.getGoodList()
+      })
+    },
+    // 分类
+    getMyClass () {
+      return getClass().then(res => {
+        if(res.code && res.code == 1){
+          this.form.categoryId = res.data[0].id
+          this.tabbar = res.data
+        }
+      })
+    },
+    // 列表
+    getGoodList () {
+      return getList({ ...this.form, page: this.list.page,pageSize:10 }) .then(res => {
+          // 处理商品列表数据,completes方法来自list混入
+          // 该方法会处理分页数据,更新list对象的data、page、finished等属性
+          // res包含接口返回的商品列表数据,completes方法会将数据添加到this.list.data中
+          this.completes(res)
+      })
+    },
+    // 点击标题切换当前页时改变样式
+    swichNav (e, item) {
+      let cur = e.currentTarget.dataset.current
+      if (this.currentTab == cur && !this.isSearchMode) {
+        return false
+      } else {
+        this.currentTab = cur
+        this.form.categoryId = item.id
+        
+        // 切换分类时清空搜索状态
+        this.clearSearchState()
+        this.resetList()
+        this.getGoodList()
+      }
+    },
+    // 判断当前滚动超过一屏时,设置tab标题滚动条。
+    checkCor () {
+      let that = this
+      // 这里计算按照实际情况进行修改,动态数据要进行动态分析
+      // 思路:窗体高度/单个分类高度 200upx 转px计算 =>得到一屏幕所显示的个数,结合后台传回分类总数进行计算
+      // 数据很多可以多次if判断然后进行滚动距离计算即可
+      if (that.currentTab > 7) {
+        that.scrollTop = 500
+      } else {
+        that.scrollTop = 0
+      }
+    },
+    tabClick (index) {
+      this.currentTabIndex = index
+    },
+    
+    // 清空搜索状态(不触发数据重新加载)
+    clearSearchState() {
+      // 清除定时器
+      if (this.searchTimer) {
+        clearTimeout(this.searchTimer)
+        this.searchTimer = null
+      }
+      
+      // 重置搜索相关状态
+      this.searchText = ''
+      this.isSearchMode = false
+      this.lastSearchText = ''
+      
+      // 确保搜索框内容被清空(强制更新)
+      // this.$nextTick(() => {
+      //   this.searchText = ''
+      // })
+    },
+    
+    // 获取空状态提示文字
+    getEmptyTitle() {
+      if (this.isSearchMode && this.searchText) {
+        return `未找到"${this.searchText}"相关商品`
+      }
+      return '暂无商品'
+    }
+  },
+  
+  // 组件销毁时清理定时器
+  beforeDestroy() {
+    if (this.searchTimer) {
+      clearTimeout(this.searchTimer)
+      this.searchTimer = null
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-content {
+  background-color: #fff;
+
+.c_header{
+    width: 100vw;
+    padding: 0 15upx;
+    box-sizing: border-box;
+    background: linear-gradient(to bottom,#3385FF, #fff);
+    &>.header_inside{
+      position: relative;
+      height: 124upx;
+      margin-top: 15upx;
+      background: #fff;
+      box-shadow: 0upx 1px 21px 0upx rgba(161, 166, 173, 0.4);
+      border-radius: 20upx;
+      padding: 35upx 30upx 30upx 30upx;
+      box-sizing: border-box;
+      &>image{
+        height: 60upx;
+        width: 60upx;
+        display: inline-block;
+        border-radius: 10upx;
+        background-color: #eee;
+        vertical-align: top;
+      }
+      &>.shop_info{
+        vertical-align: top;
+        display: inline-block;
+        height: 100%;
+        margin-left: 20upx;
+        &>.shop_title{
+          margin-top:0upx;
+          font-size:40upx;
+          font-weight: 600;
+          color: #060606;
+          width: 580upx;
+          overflow: hidden;
+          white-space: nowrap;
+        }
+      }
+    }
+  }
+
+  .fadeIn{
+    height:160upx;
+    overflow: hidden;
+    animation-name: fadeInFrames;
+    animation-duration:.3s;
+  }
+
+}
+
+page {
+  background: #fff;
+}
+
+::-webkit-scrollbar {
+  width: 0;
+  height: 0;
+  color: transparent;
+}
+
+.tab-view {
+  ::-webkit-scrollbar {
+    width: 0;
+    height: 0;
+    color: transparent;
+  }
+  width: 200upx;
+  position: fixed;
+  left: 0;
+  z-index: 10;
+  height: calc(100vh - 160upx); /* 调整高度以适应搜索框 */
+  background-color: #f0f2f6;
+  .tab-bar-item {
+    width: 200upx;
+    height: 110upx;
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 28upx;
+    color: #444;
+    font-weight: 400;
+  }
+  .active {
+    position: relative;
+    color: $mainColor;
+    font-size: 26upx;
+    background: #fff;
+  }
+
+  .active::before {
+    content: "";
+    position: absolute;
+    border-left: 8upx solid $mainColor;
+    height: 100%;
+    left: 0;
+  }
+}
+.right-box {
+  width: 100%;
+  overflow: auto;
+  height: calc(100vh - 280upx); /* 调整高度以适应搜索框 */
+  padding-left: 220upx;
+  box-sizing: border-box;
+  .page-view {
+    width: 100%;
+    overflow: hidden;
+    box-sizing: border-box;
+    margin-bottom:60upx;
+    .goods-list {
+      @include disFlex(flex-start, flex-start);
+      margin: 40upx 0;
+      .img-blo {
+        width: 200upx;
+        height: 200upx;
+        margin-right: 20upx;
+        image{
+          width: 200upx;
+          height: 200upx;
+        }
+      }
+      .goods-det {
+        padding-top: 18upx;
+        font-size: 28upx;
+        position: relative;
+        width: calc(100% - 240upx);
+        .goods-det-top {
+          .goods-name {
+            font-size: 32upx;
+            margin-bottom: 10upx;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            display: -webkit-box;
+            -webkit-line-clamp: 2;
+            line-clamp: 2;
+            -webkit-box-orient: vertical;
+            word-break: break-all;
+          }
+        }
+        .goods-price {
+          font-size: 32upx;
+          margin-top: 50upx;
+          color: $mainColor;
+          font-weight: bold;
+        }
+      }
+    }
+  }
+}
+
+/* 搜索框样式 */
+.search-wrap {
+	padding: 15upx 30upx 25upx 30upx;
+	display: flex;
+	align-items: center;
+	gap: 20upx;
+}
+
+.search-bar {
+	flex: 1;
+	background-color: #fff;
+	border-radius: 30upx;
+	padding: 2upx;
+	box-shadow: 0upx 2px 8px 0upx rgba(0, 0, 0, 0.1);
+	position: relative;
+}
+
+.search-loading {
+	padding: 10upx 30upx;
+	text-align: center;
+	background-color: #f5f5f5;
+	
+	.loading-text {
+		font-size: 24upx;
+		color: #666;
+	}
+}
+
+.buy-material-container {
+	flex-shrink: 0;
+}
+
+.buy-material-btn {
+	position: relative;
+	background: linear-gradient(135deg, #4caf50, #66bb6a, #81c784);
+	border: none;
+	border-radius: 50upx;
+	padding: 0;
+	height: 80upx;
+	min-width: 160upx;
+	box-shadow: 0 8upx 20upx rgba(76, 175, 80, 0.3);
+	overflow: hidden;
+
+
+	&:active {
+		box-shadow: 0 4upx 10upx rgba(76, 175, 80, 0.4);
+	}
+
+	.btn-content {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		height: 100%;
+		position: relative;
+		z-index: 1;
+	}
+
+	.btn-text {
+		color: white;
+		font-size: 28upx;
+		font-weight: bold;
+		text-shadow: 0 2upx 4upx rgba(0, 0, 0, 0.2);
+		margin: 0 8upx;
+	}
+}
+
+@keyframes fadeInFrames {
+	0% {
+		opacity: 0;
+		transform: translateY(0);
+		height: 0;
+	}
+	100% {
+		opacity: 1;
+		height: 180upx;
+	}
+}
+
+.buy-btn {
+  color: red;
+  border: 1upx solid red;
+  border-radius: 10upx;
+  width: 80upx;
+  text-align: center;
+  padding: 5upx 0;
+  font-size: 25upx;
+  position: absolute;
+  right: 30upx;
+  top: 150upx;
+}
+</style>