Jelajahi Sumber

feat:首页更新

钱新宇 2 minggu lalu
induk
melakukan
a89cecf115

+ 153 - 0
components/Banner/index.vue

@@ -0,0 +1,153 @@
+<template>
+  <ComponentsHeader v-bind="$props" />
+
+  <view class="banner" :style="{ padding: `0 ${gap}px` }">
+    <!-- 单图模式 -->
+    <view v-if="mode === 'single'" class="single-image">
+      <image :src="images[0]?.image || `http://iph.href.lu/400x200?text=1`" :style="{ borderRadius: `${radius}px` }" mode="aspectFill" @click="_handleClick(images[0].urlMap?.internal_url)" />
+    </view>
+
+    <!-- 轮播图模式 -->
+    <swiper
+      v-else-if="mode === 'swiper' || mode === 'swiper-no-dots'"
+      :autoplay="true"
+      :interval="swiperDuration"
+      :circular="true"
+      :indicator-dots="mode === 'swiper'"
+      class="swiper"
+      :style="{ borderRadius: `${radius}px` }"
+    >
+      <swiper-item v-for="(item, index) in images" :key="index">
+        <image @click="_handleClick(item.urlMap?.internal_url)" :src="item.image || `http://iph.href.lu/400x200?text=${index}`" :style="{ borderRadius: `${radius}px` }" mode="aspectFill" />
+      </swiper-item>
+    </swiper>
+
+    <!-- 网格模式 -->
+    <view v-else-if="mode === 'grid'" class="grid-container" :style="{ borderRadius: `${radius}px` }">
+      <view v-for="(item, index) in images.slice(0, 4)" :key="index" class="grid-item">
+        <image @click="_handleClick(item.urlMap?.internal_url)" :src="item.image || `http://iph.href.lu/400x200?text=${index}`" :style="{ borderRadius: `${radius}px` }" />
+      </view>
+    </view>
+
+    <!-- 双图模式 -->
+    <view v-else-if="mode === 'double'" class="double-container" :style="{ borderRadius: `${radius}px` }">
+      <view v-for="(item, index) in images.slice(0, 2)" :key="index" class="double-item">
+        <image @click="_handleClick(item.urlMap?.internal_url)" :src="item.image || `http://iph.href.lu/400x200?text=${index}`" :style="{ borderRadius: `${radius}px` }" mode="aspectFill" />
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import ComponentsHeader from '../ComponentsHeader';
+defineProps({
+  images: {
+    type: Array,
+    default: () => [],
+  },
+  mode: {
+    type: String,
+    default: 'single',
+    validator: (value) => ['single', 'swiper', 'swiper-no-dots', 'grid', 'double'].includes(value),
+  },
+  radius: {
+    type: Number,
+    default: 0,
+  },
+  swiperDuration: {
+    type: Number,
+    default: 3000,
+  },
+  gap: {
+    type: Number,
+    default: 0,
+  },
+  title: {
+    type: String,
+    default: '',
+  },
+  showTitle: {
+    type: Boolean,
+    default: false,
+  },
+});
+
+const _handleClick = (url) => {
+  if (url) {
+    uni.navigateTo({
+      url,
+      fail: (err) => {
+        uni.switchTab({
+          url,
+        });
+      },
+    });
+  }
+};
+</script>
+
+<style lang="less" scoped>
+.navigator {
+  width: 100%;
+  height: 100%;
+}
+.banner {
+  width: 100%;
+  overflow: hidden;
+  box-sizing: border-box;
+
+  .single-image {
+    width: 100%;
+    height: 150px;
+    overflow: hidden;
+
+    image {
+      width: 100%;
+      height: 100%;
+    }
+  }
+
+  .swiper {
+    width: 100%;
+    height: 150px;
+
+    image {
+      width: 100%;
+      height: 100%;
+    }
+  }
+
+  .grid-container {
+    display: grid;
+    grid-template-rows: repeat(2, 1fr);
+    grid-template-columns: repeat(2, 1fr);
+    gap: 4px;
+    width: 100%;
+
+    .grid-item {
+      height: 100px;
+
+      image {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+
+  .double-container {
+    display: flex;
+    gap: 4px;
+    width: 100%;
+
+    .double-item {
+      flex: 1;
+      height: 150px;
+
+      image {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+}
+</style>

+ 38 - 0
components/ComponentsHeader/index.vue

@@ -0,0 +1,38 @@
+<template>
+  <view class="header" v-if="showTitle">
+    <view class="line" />
+    {{ title }}
+  </view>
+</template>
+
+<script setup>
+import { defineProps, onMounted } from 'vue';
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: '',
+  },
+  showTitle: {
+    type: Boolean,
+    default: true,
+  },
+});
+</script>
+
+<style scoped lang="less">
+.header {
+  padding: 24rpx 0;
+  color: #333;
+  font-size: 16px;
+  display: flex;
+  align-items: center;
+  font-weight: bold;
+  .line {
+    width: 4px;
+    height: 20px;
+    background: linear-gradient(to bottom, #f89c33, #f86834);
+    margin-right: 16rpx;
+  }
+}
+</style>

+ 141 - 0
components/IconList/index.vue

@@ -0,0 +1,141 @@
+<template>
+  <view class="icon-list" :class="mode" :style="{ '--columns': getColumnCount() }">
+    <view v-for="(item, index) in displayIcons" :key="index" class="icon-item" @click="_handleClick(item.urlMap?.internal_url)">
+      <view class="icon-wrapper">
+        <image :src="item.image || `http://iph.href.lu/48x48?text=${index}`" :style="{ borderRadius: `${radius}px` }" mode="aspectFill" />
+      </view>
+      <view class="icon-text">{{ item.text }}</view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { computed } from 'vue';
+
+const props = defineProps({
+  icons: {
+    type: Array,
+    default: () => [1, 2, 3, 4],
+  },
+  mode: {
+    type: String,
+    default: 'row4',
+    validator: (value) => ['row4', 'row8', 'row5', 'row10'].includes(value),
+  },
+  radius: {
+    type: Number,
+    default: 0,
+  },
+});
+
+const getColumnCount = () => {
+  switch (props.mode) {
+    case 'row4':
+    case 'row8':
+      return 4;
+    case 'row5':
+    case 'row10':
+      return 5;
+    default:
+      return 4;
+  }
+};
+
+const getMaxItems = () => {
+  switch (props.mode) {
+    case 'row4':
+      return 4;
+    case 'row8':
+      return 8;
+    case 'row5':
+      return 5;
+    case 'row10':
+      return 10;
+    default:
+      return 4;
+  }
+};
+
+const _handleClick = (url) => {
+  if (url) {
+    uni.navigateTo({
+      url,
+      fail: (err) => {
+        uni.switchTab({
+          url,
+        });
+      },
+    });
+  }
+};
+
+const displayIcons = computed(() => {
+  return props.icons.slice(0, getMaxItems());
+});
+</script>
+
+<style lang="less" scoped>
+.navigator {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+.icon-list {
+  display: grid;
+  grid-template-columns: repeat(var(--columns), 1fr);
+  gap: 16rpx;
+  width: 100%;
+  padding: 16px;
+  box-sizing: border-box;
+  background-color: #fff;
+  border-radius: 8px;
+
+  .icon-item {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+    align-items: center;
+
+    .icon-wrapper {
+      width: 44px;
+      height: 44px;
+      overflow: hidden;
+
+      image {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+    }
+
+    .icon-text {
+      width: 100%;
+      overflow: hidden;
+      color: #333;
+      font-size: 12px;
+      white-space: nowrap;
+      text-align: center;
+      text-overflow: ellipsis;
+    }
+  }
+}
+
+.row4 {
+  grid-template-rows: 1fr;
+}
+
+.row8 {
+  grid-template-rows: repeat(2, 1fr);
+}
+
+.row5 {
+  grid-template-rows: 1fr;
+}
+
+.row10 {
+  grid-template-rows: repeat(2, 1fr);
+}
+</style>

+ 157 - 0
components/Notice/index.vue

@@ -0,0 +1,157 @@
+<template>
+  <view class="notice-container" :style="{ padding: `0 ${gap}px` }">
+    <view class="notice-wrapper">
+      <view class="notice-icon">公告</view>
+      <view class="notice-content">
+        <swiper class="notice-swiper" vertical :circular="true" :autoplay="true" :interval="swiper * 1000" :duration="500" :display-multiple-items="1" @change="handleChange">
+          <swiper-item v-for="(notice, index) in noticeList" :key="index" @click="handleNoticeClick(notice)">
+            <view class="notice-item">{{ notice.text }}</view>
+          </swiper-item>
+        </swiper>
+        <view class="notice-more">更多 ></view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
+
+// 定义props
+const props = defineProps({
+  noticeList: {
+    type: Array,
+    default: () => [],
+    validator: (value) => {
+      return value.every((item) => typeof item.text === 'string' && typeof item.url === 'string');
+    },
+  },
+  gap: {
+    type: Number,
+    default: 0,
+  },
+  swiper: {
+    type: Number,
+    default: 3,
+  },
+});
+
+// 响应式状态
+const currentIndex = ref(0);
+let timer = null;
+
+// 计算循环列表
+const displayList = computed(() => {
+  return [props.noticeList[props.noticeList.length - 1], ...props.noticeList, props.noticeList[0]];
+});
+
+// 处理点击事件
+const handleNoticeClick = (notice) => {
+  if (notice.url) {
+    // window.location.href = notice.url;
+    uni.navigateTo({
+      url: notice.url,
+    });
+  }
+};
+
+// 启动轮播
+const startSwiper = () => {
+  if (props.noticeList.length <= 1) return;
+
+  // 设置初始位置
+  currentIndex.value = 1;
+
+  timer = setInterval(() => {
+    const nextIndex = currentIndex.value + 1;
+    if (nextIndex >= displayList.value.length - 1) {
+      currentIndex.value = nextIndex;
+      // 当到达最后一个元素时,等待过渡动画完成后重置到第一个元素
+      setTimeout(() => {
+        currentIndex.value = 1;
+      }, 500);
+    } else {
+      currentIndex.value = nextIndex;
+    }
+  }, props.swiper * 1000);
+};
+
+// 监听列表变化
+watch(
+  () => props.noticeList.length,
+  () => {
+    if (timer) {
+      clearInterval(timer);
+    }
+    startSwiper();
+  }
+);
+
+// 组件挂载时启动轮播
+onMounted(() => {
+  startSwiper();
+});
+
+// 组件卸载时清理定时器
+onUnmounted(() => {
+  if (timer) {
+    clearInterval(timer);
+  }
+});
+</script>
+
+<style lang="less" scoped>
+.notice-container {
+  margin: 4px 0;
+  position: relative;
+  .notice-wrapper {
+    display: flex;
+    align-items: center;
+    height: 40px;
+    background: #fff;
+    border-radius: 130rpx 130rpx 130rpx 130rpx;
+  }
+
+  .notice-icon {
+    width: 68rpx;
+    height: 32rpx;
+    color: #f89c33;
+    font-size: 14px;
+    border: 1px solid #f89c33;
+    border-radius: 8rpx;
+    text-align: center;
+    line-height: 32rpx;
+    margin: 0 10px;
+  }
+
+  .notice-content {
+    flex: 1;
+    height: 40px;
+    overflow: hidden;
+  }
+
+  .notice-slider {
+    height: 100%;
+  }
+
+  .notice-item {
+    height: 40px;
+    line-height: 40px;
+    color: #333;
+    font-size: 14px;
+    cursor: pointer;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    width: 80%;
+  }
+  .notice-more {
+    color: #999;
+    position: absolute;
+    right: 25px;
+    top: 50%;
+    transform: translateY(-50%);
+    font-size: 12px;
+  }
+}
+</style>

+ 14 - 2
pages.json

@@ -1,6 +1,5 @@
 {
   "pages": [
-    //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
     {
       "path": "pages/index/index",
       "style": {
@@ -8,6 +7,19 @@
         "enablePullDownRefresh": true
       }
     },
+    {
+      "path": "pages/product/list",
+      "style": {
+        "navigationBarTitleText": "产品列表",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/wait/index",
+      "style": {
+        "navigationBarTitleText": "活动页面"
+      }
+    },
     {
       "path": "pages/car/index",
       "style": {
@@ -108,7 +120,7 @@
         "navigationBarTitleText": "兑换记录"
       }
     },
-	{
+    {
       "path": "pages/score/orders_detail",
       "style": {
         "navigationBarTitleText": "兑换详情"

+ 123 - 62
pages/index/index.vue

@@ -1,6 +1,8 @@
 <template>
   <view>
+    <view class="page-title" />
     <!-- 轮播图 -->
+
     <view class="banner_box">
       <view class="banner_list" v-if="bannerList.length">
         <swiper class="banner_swiper" :autoplay="true">
@@ -10,6 +12,21 @@
         </swiper>
       </view>
     </view>
+    <view class="icon-list">
+      <icon-list mode="row8" :icons="icons" :gap="0" />
+    </view>
+    <view class="notice-list">
+      <notice-list :noticeList="[{ text: '开邻智数新版本发布啦~' }, { text: '更多新功能敬请期待~' }]" gap="18" />
+    </view>
+    <view class="header-title">
+      <components-header title="专区导航" />
+    </view>
+    <view class="banner-list">
+      <banner-list :images="banners" mode="grid" :gap="16" />
+    </view>
+    <view class="header-title">
+      <components-header title="推荐商品" />
+    </view>
     <view class="product_box">
       <view class="to_bottom" v-if="!productList.length"> -----还没有产品啦-----</view>
       <!-- 产品列表 -->
@@ -47,7 +64,7 @@
     </view>
     <view class="to_bottom" v-if="isLast" style="display: flex; justify-content: center">
       -----
-      <view :style="!productList.length && toSelectedCity ? 'font-weight:bold;font-size:36rpx' : ''">{{ !productList.length && toSelectedCity ? "请点击左上角选择你的城市" : "到底啦" }}</view> -----
+      <view :style="!productList.length && toSelectedCity ? 'font-weight:bold;font-size:36rpx' : ''">{{ !productList.length && toSelectedCity ? '请点击左上角选择你的城市' : '到底啦' }}</view> -----
     </view>
     <uni-popup ref="addFollow" type="center" class="center_popup">
       <FollowPopup :closePopup="closePopup" />
@@ -56,9 +73,29 @@
 </template>
 
 <script>
-import FollowPopup from "@/components/FollowPopup/FollowPopup.vue";
+import FollowPopup from '@/components/FollowPopup/FollowPopup.vue';
+import IconList from '@/components/IconList';
+import NoticeList from '@/components/Notice';
+import BannerList from '@/components/Banner';
+import ComponentsHeader from '@/components/ComponentsHeader';
+const Icons = [
+  { image: 'https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/c/1/s/92/up/20250521/682d3fed51fa2.ng', text: '全品', urlMap: { internal_url: '/pages/product/list' } },
+  { image: 'https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/c/1/s/92/up/20250521/682d3ffdea775.ng', text: '医保价', urlMap: { internal_url: '/pages/wait/index?id=1' } },
+  { image: 'https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/c/1/s/92/up/20250521/682d400fd5932.ng', text: '行业资讯', urlMap: { internal_url: '/pages/wait/index?id=2' } },
+  { image: 'https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/c/1/s/92/up/20250521/682d401d6a885.ng', text: '直播', urlMap: { internal_url: '/pages/wait/index?id=3' } },
+  { image: 'https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/c/1/s/92/up/20250521/682d402ddaeb6.ng', text: '促销活动', urlMap: { internal_url: '/pages/wait/index?id=4' } },
+  { image: 'https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/c/1/s/92/up/20250521/682d404366cf8.ng', text: '拼团包邮', urlMap: { internal_url: '/pages/wait/index?id=5' } },
+  { image: 'https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/c/1/s/92/up/20250521/682d404c65d9f.ng', text: '积分商城', urlMap: { internal_url: '/pages/score/index' } },
+  { image: 'https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/c/1/s/92/up/20250521/682d40561a23b.ng', text: '领卷中心', urlMap: { internal_url: '/pages/coupon/index' } },
+];
+const Banners = [
+  { image: 'https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/index/active.png', text: '区域活动', urlMap: { internal_url: '/pages/wait/index?id=8' } },
+  { image: 'https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/index/b2b.png', text: 'B2B专供', urlMap: { internal_url: '/pages/wait/index?id=9' } },
+  { image: 'https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/index/exp.png', text: '近效期', urlMap: { internal_url: '/pages/wait/index?id=10' } },
+  { image: 'https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/index/heath.png', text: '大健康', urlMap: { internal_url: '/pages/wait/index?id=11' } },
+];
 export default {
-  components: { FollowPopup },
+  components: { FollowPopup, IconList, NoticeList, BannerList, ComponentsHeader },
   data() {
     return {
       // 轮播图
@@ -67,7 +104,7 @@ export default {
       productList: [],
       // 请求参数
       requestParam: {
-        name: "",
+        name: '',
         page: 1,
       },
       // 是否最后一页
@@ -77,11 +114,13 @@ export default {
       // 是否显示价格
       isShowPrice: false,
       // 城市名称
-      cityName: "选城市",
+      cityName: '选城市',
       // 选择城市
       toSelectedCity: false,
       // 是否是管理员
       isManager: false,
+      icons: Icons,
+      banners: Banners,
     };
   },
   onLoad() {
@@ -89,7 +128,7 @@ export default {
     //分享按钮
     uni.showShareMenu({
       withShareTicket: true,
-      menus: ["shareAppMessage", "shareTimeline"],
+      menus: ['shareAppMessage', 'shareTimeline'],
     });
     // #endif
   },
@@ -98,13 +137,13 @@ export default {
     let shareList = getApp().globalData.shareList;
     // 获取分享信息
     let shareObj = {
-      title: "999智控终端平台\n药优惠 得积分 兑豪礼",
-      path: "/pages/index/index",
-      imageUrl: "",
+      title: '999智控终端平台\n药优惠 得积分 兑豪礼',
+      path: '/pages/index/index',
+      imageUrl: '',
     };
     // 循环列表
     for (let i in shareList) {
-      if (shareList[i].pages == "pages/index/index") {
+      if (shareList[i].pages == 'pages/index/index') {
         shareObj.path = shareList[i].path ? shareList[i].path : shareObj.path;
         shareObj.title = shareList[i].title ? `999智控终端平台\n${shareList[i].title}` : shareObj.title;
         shareObj.imageUrl = shareList[i].image_url ? shareList[i].image_url : shareObj.imageUrl;
@@ -119,7 +158,7 @@ export default {
     // 城市名
     this.cityName = this.$checkAccess.getCity();
     // 选城市
-    (this.cityName = this.cityName ? this.cityName : "选城市"),
+    (this.cityName = this.cityName ? this.cityName : '选城市'),
       // 登录并且未选择城市,才可以选择城市
       (this.toSelectedCity = !this.$checkAccess.getCity() ? true : false);
     // 数据
@@ -127,12 +166,12 @@ export default {
     //未选城市先弹窗提醒选择城市
     if (!this.$checkAccess.getCity() && this.$checkAccess.checkLogin()) {
       uni.showModal({
-        title: "",
-        content: "请先选择城市",
+        title: '',
+        content: '请先选择城市',
         success: (res) => {
           if (res.confirm) {
             uni.navigateTo({
-              url: "/pages/user/info",
+              url: '/pages/user/info',
             });
           }
         },
@@ -142,28 +181,28 @@ export default {
     //如果已选城市且没有添加客服每天弹窗一次
     if (this.$checkAccess.getCity() && this.$checkAccess.getFollowQrcode()) {
       // 获取弹出时间
-      let followPopupTime = uni.getStorageSync("followPopupTime");
+      let followPopupTime = uni.getStorageSync('followPopupTime');
       // 现在的时候
       let nowTime = new Date().getTime();
       // 时间戳
       followPopupTime = followPopupTime ? followPopupTime : 0;
       // 弹出结果
       if (followPopupTime <= 0 || followPopupTime - nowTime > 86400000) {
-        this.$refs.addFollow.open("center");
-        uni.setStorageSync("followPopupTime", nowTime);
+        this.$refs.addFollow.open('center');
+        uni.setStorageSync('followPopupTime', nowTime);
       }
     }
     this.isManager = this.$checkAccess.isManager();
     // 没有数据的话,或者请求中,不允许刷新
     if (this.isReqing) return;
     // 获取列表
-    this.$http.request("/api/banner/get_list").then((re) => {
-      if (re.code === "success") {
+    this.$http.request('/api/banner/get_list').then((re) => {
+      if (re.code === 'success') {
         this.bannerList = re.data;
       }
     });
     // 请求参数
-    this.requestParam.name = "";
+    this.requestParam.name = '';
     // 请求参数
     this.requestParam.page = 1;
     // 是否是最后一页
@@ -171,17 +210,17 @@ export default {
     // 设置请求中
     this.isReqing = true;
     // 请求
-    this.$http.request("api/product/get_list", this.requestParam).then((re) => {
+    this.$http.request('api/product/get_list', this.requestParam).then((re) => {
       // 设置非请求中
       this.isReqing = false;
       // 成功结果
-      if (re.code == "success") {
-		// 如果是最后一页
+      if (re.code == 'success') {
+        // 如果是最后一页
         if (re.data.last_page <= this.requestParam.page) this.isLast = true;
-		// 赋值
+        // 赋值
         this.productList = re.data.data;
-		// 获取下一页
-		this.getMore(re);
+        // 获取下一页
+        this.getMore(re);
       }
     });
   },
@@ -195,17 +234,17 @@ export default {
     // 设置请求中
     this.isReqing = true;
     // 请求列表
-    this.$http.request("api/product/get_list", this.requestParam).then((re) => {
+    this.$http.request('api/product/get_list', this.requestParam).then((re) => {
       // 设置非请求中
       this.isReqing = false;
       // 成功结果
-      if (re.code == "success") {
-		// 如果是最后一页
+      if (re.code == 'success') {
+        // 如果是最后一页
         if (re.data.last_page <= this.requestParam.page) this.isLast = true;
-		// 赋值
+        // 赋值
         this.productList = re.data.data;
-		// 获取下一页
-		this.getMore(re);
+        // 获取下一页
+        this.getMore(re);
       }
     });
     uni.stopPullDownRefresh();
@@ -222,11 +261,11 @@ export default {
     // 设置请求中
     this.isReqing = true;
     // 请求列表
-    this.$http.request("api/product/get_list", this.requestParam).then((re) => {
+    this.$http.request('api/product/get_list', this.requestParam).then((re) => {
       // 设置非请求中
       this.isReqing = false;
       // 成功结果
-      if (re.code == "success") {
+      if (re.code == 'success') {
         // 最后一页
         if (re.data.last_page <= this.requestParam.page) this.isLast = true;
         // 追加数据
@@ -235,28 +274,28 @@ export default {
     });
   },
   methods: {
-	// 请求加载下一页
-	getMore(callback){
-		// 首页不足10个,并且下一页存在大于当前页
-		if( this.productList.length < 10 && callback.data.last_page > this.requestParam.page ){
-			// 最后一页不再请求
-			if (this.isLast) return;
-			// 增加一页
-			this.requestParam.page = this.requestParam.page + 1;
-			// 请求列表
-			this.$http.request("api/product/get_list", this.requestParam).then((re) => {
-			  // 成功结果
-			  if (re.code == "success") {
-			    // 最后一页
-			    if (re.data.last_page <= this.requestParam.page) this.isLast = true;
-			    // 追加数据
-			    this.productList.push(...re.data.data);
-				// 获取下一页
-				this.getMore(re);
-			  }
-			});
-		}
-	},
+    // 请求加载下一页
+    getMore(callback) {
+      // 首页不足10个,并且下一页存在大于当前页
+      if (this.productList.length < 10 && callback.data.last_page > this.requestParam.page) {
+        // 最后一页不再请求
+        if (this.isLast) return;
+        // 增加一页
+        this.requestParam.page = this.requestParam.page + 1;
+        // 请求列表
+        this.$http.request('api/product/get_list', this.requestParam).then((re) => {
+          // 成功结果
+          if (re.code == 'success') {
+            // 最后一页
+            if (re.data.last_page <= this.requestParam.page) this.isLast = true;
+            // 追加数据
+            this.productList.push(...re.data.data);
+            // 获取下一页
+            this.getMore(re);
+          }
+        });
+      }
+    },
     searchChange(e) {
       // 如果没有搜索词
       if (!this.requestParam.name) {
@@ -273,11 +312,11 @@ export default {
       // 设置请求中
       this.isReqing = true;
       // 请求列表
-      this.$http.request("api/product/get_list", this.requestParam).then((re) => {
+      this.$http.request('api/product/get_list', this.requestParam).then((re) => {
         // 设置非请求中
         this.isReqing = false;
         // 成功结果
-        if (re.code == "success") {
+        if (re.code == 'success') {
           this.productList = re.data.data;
           if (re.data.data.length && re.data.last_page >= this.requestParam.page) this.isLast = true;
         }
@@ -285,14 +324,14 @@ export default {
     },
     toDetail(item) {
       uni.navigateTo({
-        url: "/pages/product/index?product_id=" + item.id,
+        url: '/pages/product/index?product_id=' + item.id,
       });
     },
     navLottery(url) {
       // 没有路径,不跳转
       if (!url) return;
       // 判断是不是小程序链接
-      if (url.includes("http")) {
+      if (url.includes('http')) {
         // 转码
         let link_url = encodeURIComponent(url);
         // 跳转到webview
@@ -310,7 +349,7 @@ export default {
       this.$refs.addFollow.close();
       //存key以及时间
       uni.setStorage({
-        key: "followPopupTime",
+        key: 'followPopupTime',
         data: new Date().getTime(),
       });
     },
@@ -319,6 +358,16 @@ export default {
 </script>
 
 <style lang="less">
+.page-title {
+  //渐变加透明;
+  // background: linear-gradient(to bottom, rgba(245, 52, 29, 1) 0%, rgba(255, 255, 255, 0) 100%);
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 588rpx;
+  z-index: -1;
+}
 .search_fixed {
   top: var(--window-top);
   left: 0rpx;
@@ -397,9 +446,11 @@ export default {
       height: 382rpx;
       line-height: 382rpx;
       text-align: center;
+      border-radius: 8px;
       .image {
         width: 680rpx;
         height: 382rpx;
+        border-radius: 8px;
       }
     }
   }
@@ -407,7 +458,7 @@ export default {
 .product_box {
   display: block;
   overflow: hidden;
-  margin: 20rpx auto;
+  margin: -20rpx auto 20rpx;
   padding: 0rpx 35rpx;
   .product_list {
     display: block;
@@ -481,4 +532,14 @@ export default {
     }
   }
 }
+.icon-list {
+  margin-top: 24rpx;
+  padding: 0 35rpx;
+}
+.notice-list {
+  margin-top: 24rpx;
+}
+.header-title {
+  padding: 0 35rpx;
+}
 </style>

+ 430 - 0
pages/product/list.vue

@@ -0,0 +1,430 @@
+<template>
+  <view style="padding-top: 70rpx">
+    <!-- 轮播图 -->
+    <view class="search_fixed">
+      <view class="search_box">
+        <input class="search_input" type="text" v-model="requestParam.name" @input="searchChange" placeholder="请输入产品名称搜索" />
+        <button class="search_btn" @click.stop="searchOpen()" data-eventsync="true">搜索</button>
+      </view>
+    </view>
+    <view class="product_box">
+      <view class="to_bottom" v-if="!productList.length"> -----还没有产品啦-----</view>
+      <!-- 产品列表 -->
+      <view class="product_list">
+        <!-- Vue3 项目部分小程序端事件延迟或调用失败 在执行事件的元素上添加 data-eventsync="true" 属性以解决此问题 -->
+        <view @click="toDetail(item)" data-eventsync="true" class="product_item" v-for="(item, index) in productList" :key="index">
+          <image class="product_image" :src="item.thumb" mode=""></image>
+          <view class="product_name">
+            <text v-if="item.promo_title" class="regiment_title">{{ item.promo_title }}</text>
+            <text v-if="item.regiment_title" class="regiment_title">{{ item.regiment_title }}</text>
+            <text>{{ item.name }}</text></view
+          >
+          <view class="product_spec"
+            ><text>{{ item.spec }}</text></view
+          >
+          <view class="stock_price">
+            <view class="product_price" v-if="isShowPrice">
+              <text>¥{{ item.price }} </text>
+            </view>
+            <view class="product_stock">剩{{ item.stock }}个</view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view class="to_bottom" v-if="isLast" style="display: flex; justify-content: center">
+      -----
+      <view :style="!productList.length && toSelectedCity ? 'font-weight:bold;font-size:36rpx' : ''">{{ !productList.length && toSelectedCity ? '请点击左上角选择你的城市' : '到底啦' }}</view> -----
+    </view>
+    <uni-popup ref="addFollow" type="center" class="center_popup">
+      <FollowPopup :closePopup="closePopup" />
+    </uni-popup>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      // 轮播图
+      bannerList: [],
+      // 产品列表
+      productList: [],
+      // 请求参数
+      requestParam: {
+        name: '',
+        page: 1,
+      },
+      // 是否最后一页
+      isLast: false,
+      // 是否请求中
+      isReqing: false,
+      // 是否显示价格
+      isShowPrice: false,
+      // 城市名称
+      cityName: '选城市',
+      // 选择城市
+      toSelectedCity: false,
+      // 是否是管理员
+      isManager: false,
+    };
+  },
+  onLoad() {
+    // #ifdef MP-WEIXIN
+    //分享按钮
+    uni.showShareMenu({
+      withShareTicket: true,
+      menus: ['shareAppMessage', 'shareTimeline'],
+    });
+    // #endif
+  },
+  onShareAppMessage(obj) {
+    // 获取分享信息
+    let shareList = getApp().globalData.shareList;
+    // 获取分享信息
+    let shareObj = {
+      title: '999智控终端平台\n药优惠 得积分 兑豪礼',
+      path: '/pages/index/index',
+      imageUrl: '',
+    };
+    // 循环列表
+    for (let i in shareList) {
+      if (shareList[i].pages == 'pages/index/index') {
+        shareObj.path = shareList[i].path ? shareList[i].path : shareObj.path;
+        shareObj.title = shareList[i].title ? `999智控终端平台\n${shareList[i].title}` : shareObj.title;
+        shareObj.imageUrl = shareList[i].image_url ? shareList[i].image_url : shareObj.imageUrl;
+      }
+    }
+    // 返回分享信息
+    return shareObj;
+  },
+  onShow() {
+    // 是否显示价格
+    this.isShowPrice = this.$checkAccess.checkShowPrice();
+    // 城市名
+    this.cityName = this.$checkAccess.getCity();
+    // 选城市
+    (this.cityName = this.cityName ? this.cityName : '选城市'),
+      // 登录并且未选择城市,才可以选择城市
+      (this.toSelectedCity = !this.$checkAccess.getCity() ? true : false);
+    // 数据
+
+    //未选城市先弹窗提醒选择城市
+    if (!this.$checkAccess.getCity() && this.$checkAccess.checkLogin()) {
+      uni.showModal({
+        title: '',
+        content: '请先选择城市',
+        success: (res) => {
+          if (res.confirm) {
+            uni.navigateTo({
+              url: '/pages/user/info',
+            });
+          }
+        },
+      });
+    }
+
+    //如果已选城市且没有添加客服每天弹窗一次
+    if (this.$checkAccess.getCity() && this.$checkAccess.getFollowQrcode()) {
+      // 获取弹出时间
+      let followPopupTime = uni.getStorageSync('followPopupTime');
+      // 现在的时候
+      let nowTime = new Date().getTime();
+      // 时间戳
+      followPopupTime = followPopupTime ? followPopupTime : 0;
+      // 弹出结果
+      if (followPopupTime <= 0 || followPopupTime - nowTime > 86400000) {
+        this.$refs.addFollow.open('center');
+        uni.setStorageSync('followPopupTime', nowTime);
+      }
+    }
+    this.isManager = this.$checkAccess.isManager();
+    // 没有数据的话,或者请求中,不允许刷新
+    if (this.isReqing) return;
+    // 获取列表
+    this.$http.request('/api/banner/get_list').then((re) => {
+      if (re.code === 'success') {
+        this.bannerList = re.data;
+      }
+    });
+    // 请求参数
+    this.requestParam.name = '';
+    // 请求参数
+    this.requestParam.page = 1;
+    // 是否是最后一页
+    this.isLast = false;
+    // 设置请求中
+    this.isReqing = true;
+    // 请求
+    this.$http.request('api/product/get_list', this.requestParam).then((re) => {
+      // 设置非请求中
+      this.isReqing = false;
+      // 成功结果
+      if (re.code == 'success') {
+        // 如果是最后一页
+        if (re.data.last_page <= this.requestParam.page) this.isLast = true;
+        // 赋值
+        this.productList = re.data.data;
+        // 获取下一页
+        this.getMore(re);
+      }
+    });
+  },
+  onPullDownRefresh() {
+    // 如果请求中,不允许请求,
+    if (this.isReqing) return false;
+    // 初始化页码为1
+    this.requestParam.page = 1;
+    // 是否是最后一页
+    this.isLast = false;
+    // 设置请求中
+    this.isReqing = true;
+    // 请求列表
+    this.$http.request('api/product/get_list', this.requestParam).then((re) => {
+      // 设置非请求中
+      this.isReqing = false;
+      // 成功结果
+      if (re.code == 'success') {
+        // 如果是最后一页
+        if (re.data.last_page <= this.requestParam.page) this.isLast = true;
+        // 赋值
+        this.productList = re.data.data;
+        // 获取下一页
+        this.getMore(re);
+      }
+    });
+    uni.stopPullDownRefresh();
+  },
+  onReachBottom() {
+    // 如果页码是0,避免第一页重复
+    if (this.requestParam.page < 1) return;
+    // 最后一页不再请求
+    if (this.isLast) return;
+    // 请求中,不再请求
+    if (this.isReqing) return;
+    // 增加一页
+    this.requestParam.page = this.requestParam.page + 1;
+    // 设置请求中
+    this.isReqing = true;
+    // 请求列表
+    this.$http.request('api/product/get_list', this.requestParam).then((re) => {
+      // 设置非请求中
+      this.isReqing = false;
+      // 成功结果
+      if (re.code == 'success') {
+        // 最后一页
+        if (re.data.last_page <= this.requestParam.page) this.isLast = true;
+        // 追加数据
+        this.productList.push(...re.data.data);
+      }
+    });
+  },
+  methods: {
+    // 请求加载下一页
+    getMore(callback) {
+      // 首页不足10个,并且下一页存在大于当前页
+      if (this.productList.length < 10 && callback.data.last_page > this.requestParam.page) {
+        // 最后一页不再请求
+        if (this.isLast) return;
+        // 增加一页
+        this.requestParam.page = this.requestParam.page + 1;
+        // 请求列表
+        this.$http.request('api/product/get_list', this.requestParam).then((re) => {
+          // 成功结果
+          if (re.code == 'success') {
+            // 最后一页
+            if (re.data.last_page <= this.requestParam.page) this.isLast = true;
+            // 追加数据
+            this.productList.push(...re.data.data);
+            // 获取下一页
+            this.getMore(re);
+          }
+        });
+      }
+    },
+    searchChange(e) {
+      // 如果没有搜索词
+      if (!this.requestParam.name) {
+        this.searchOpen();
+      }
+    },
+    searchOpen() {
+      // 请求中,不再请求
+      if (this.isReqing) return;
+      // 是否是最后一页
+      this.isLast = false;
+      // 初始化页码为1
+      this.requestParam.page = 1;
+      // 设置请求中
+      this.isReqing = true;
+      // 请求列表
+      this.$http.request('api/product/get_list', this.requestParam).then((re) => {
+        // 设置非请求中
+        this.isReqing = false;
+        // 成功结果
+        if (re.code == 'success') {
+          this.productList = re.data.data;
+          if (re.data.data.length && re.data.last_page >= this.requestParam.page) this.isLast = true;
+        }
+      });
+    },
+    toDetail(item) {
+      uni.navigateTo({
+        url: '/pages/product/index?product_id=' + item.id,
+      });
+    },
+    navLottery(url) {
+      // 没有路径,不跳转
+      if (!url) return;
+      // 判断是不是小程序链接
+      if (url.includes('http')) {
+        // 转码
+        let link_url = encodeURIComponent(url);
+        // 跳转到webview
+        uni.redirectTo({
+          url: `/pages/webview/index?link_url=${link_url}`,
+        });
+      } else {
+        // 跳转到webview
+        uni.navigateTo({
+          url: url,
+        });
+      }
+    },
+    closePopup() {
+      this.$refs.addFollow.close();
+      //存key以及时间
+      uni.setStorage({
+        key: 'followPopupTime',
+        data: new Date().getTime(),
+      });
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.search_fixed {
+  top: var(--window-top);
+  left: 0rpx;
+  width: 750rpx;
+  display: block;
+  position: fixed;
+  margin: 0rpx auto;
+  padding: 20rpx 20rpx;
+  background-color: #ffffff;
+  box-sizing: border-box;
+  .search_box {
+    width: 750rpx;
+    height: 60rpx;
+    display: block;
+    position: relative;
+
+    .search_input {
+      z-index: 0;
+      float: left;
+      width: 600rpx;
+      height: 56rpx;
+      display: block;
+      font-size: 24rpx;
+      padding-left: 20rpx;
+      position: relative;
+      border-top-left-radius: 40rpx;
+      border-bottom-left-radius: 40rpx;
+      border: 2rpx solid #dddddd;
+    }
+    .search_btn {
+      top: 0rpx;
+      z-index: 9;
+      right: 40rpx;
+      color: #ffffff;
+      position: absolute;
+      display: block;
+      width: 120rpx;
+      height: 60rpx;
+      font-size: 24rpx;
+      margin: 0rpx 0rpx;
+      padding: 0rpx 0rpx;
+      line-height: 60rpx;
+      border-radius: 40rpx;
+      background-color: #e03519;
+    }
+  }
+}
+.product_box {
+  display: block;
+  overflow: hidden;
+  margin: 40rpx auto;
+  padding: 0rpx 35rpx;
+  .product_list {
+    display: block;
+    overflow: hidden;
+    margin: 0rpx auto;
+    .product_item {
+      float: left;
+      width: 320rpx;
+      height: 520rpx;
+      display: block;
+      overflow: hidden;
+      margin: 20rpx 0rpx;
+      margin-right: 40rpx;
+      background-color: #ffffff;
+      border-radius: 20rpx;
+      .product_image {
+        width: 320rpx;
+        height: 320rpx;
+      }
+      .product_name {
+        height: 80rpx;
+        font-size: 30rpx;
+        line-height: 40rpx;
+        overflow: hidden;
+        margin: 10rpx 0rpx;
+        padding: 0rpx 10rpx;
+        text-overflow: ellipsis;
+        .regiment_title {
+          background-color: red;
+          color: #f9f9f9;
+        }
+      }
+      .product_spec {
+        height: 30rpx;
+        color: #999999;
+        font-size: 24rpx;
+        line-height: 30rpx;
+        padding: 0rpx 10rpx;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+      }
+      .stock_price {
+        color: #dddddd;
+        font-size: 20rpx;
+        overflow: hidden;
+        line-height: 30rpx;
+        padding: 0rpx 10rpx;
+        .product_price {
+          float: left;
+          color: red;
+          font-size: 30rpx;
+          line-height: 60rpx;
+          .product_market {
+            font-size: 24rpx;
+            color: #999999;
+            line-height: 30rpx;
+            vertical-align: top;
+            text-decoration: line-through;
+          }
+        }
+        .product_stock {
+          float: right;
+          font-size: 20rpx;
+          line-height: 60rpx;
+        }
+      }
+    }
+    .product_item:nth-child(even) {
+      margin-right: 0rpx;
+    }
+  }
+}
+</style>

+ 43 - 0
pages/wait/index.vue

@@ -0,0 +1,43 @@
+<template>
+  <view class="page">
+    <image :src="imgUrl" mode="widthFix" />
+    <view v-if="!imagePage">即将开放,敬请期待</view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      imagePage: false,
+      imgUrl: '',
+    };
+  },
+  onLoad(param) {
+    this.imgUrl = `https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/index/banner_${param.id}.png`;
+    this.imagePage = ['3', '11'].includes(param.id);
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.page {
+  width: 100vw;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  > image {
+    height: 300rpx;
+    width: 100%;
+  }
+  > view {
+    flex: 1;
+    font-size: 30rpx;
+    color: #999;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+}
+</style>