uniapp相关记录

一、自定义我的物品组件 my_goods.vue

<template>
  <view class="goods-item">
    <!-- 左侧 -->
    <view class="goods-item-left">
      <radio :checked="goods.goods_state" color="#c00000" v-if="showRadio" @click="radioClickhandler"></radio>
      <image :src="goods.goods_small_logo || defaultPic" class="goods-pic"></image>
    </view>
    <!-- 右侧 -->
    <view class="goods-item-right">
      <!-- 商品名字 -->
      <view class="goods-name">{{goods.goods_name}}</view>
      <view class="goods-info-box">
        <view class="good-price">{{goods.goods_price}}</view>
        <uni-number-box :min="1" :max="9999" :value="goods.goods_count" v-if="showNum"
          @change="numChangeHandler"></uni-number-box>
      </view>
    </view>
  </view>
</template>

<script>
  export default {
    props: {
      // 商品的信息对象
      goods: {
        type: Object,
        defaul: {},
      },
      showRadio: {
        type: Boolean,
        // 默认不展示 radio 组件
        default: false
      },
      showNum: {
        type: Boolean,
        default: false
      }
    },
    data() {
      return {
        // 默认的图片
        defaultPic: 'https://img3.doubanio.com/f/movie/8dd0c794499fe925ae2ae89ee30cd225750457b4/pics/movie/celebrity-default-medium.png',
      };
    },
    methods: {
      // radio 组件的点击事件处理函数
      radioClickhandler() {
        this.$emit('radio-change', {
          goods_id: this.goods.goods_id,
          goods_state: !this.goods.goods_state
        })
      },
      // 监听购物车商品数量变化的事件
      numChangeHandler(val) {
        this.$emit('num-change', {
          goods_id: this.goods.goods_id,
          goods_count: +val
        })
      }
    }
  }
</script>

<style lang="scss">
  .goods-item {
    display: flex;
    padding: 10px 5px;
    border-bottom: 1px solid #dedede;
    background-color: #fff;

    .goods-item-left {
      margin-right: 5px;
      display: flex;
      justify-content: center;
      align-items: center;

      .goods-pic {
        width: 100px;
        height: 100px;
        display: block;
      }
    }

    .goods-item-right {
      display: flex;
      flex: 1;
      flex-direction: column;
      justify-content: space-between;

      .goods-name {
        font-size: 13px;
      }

      .goods-info-box {
        display: flex;
        justify-content: space-between;
        align-items: center;

        .good-price {
          color: #c00000;
          font-size: 16px;
        }
      }
    }
  }
</style>

二、自定义商品列表组件 good_list.vue

<template>
  <view>
    <view class="goods-list">
      <view v-for="(goods,i) in goodsList" :key="i" @click="gotoDetail(goods)">
        <my-goods :goods="goods"></my-goods>
      </view>
    </view>
  </view>
</template>

<script>
	import {myGoods} from '@/components/my-goods/my-goods.vue'
  export default {
	components: {myGoods},  
	  
    data() {
        return {
            // 请求参数对象
            queryObj: {
              query: '',
              cid: '',
              pagenum: 1,
              pagesize: 10
            },
            goodsList: [],
            total: 0,
            isLoading: false
          }
        },
        onLoad(options) {
          this.queryObj.query = options.query || ''
          this.queryObj.cid = options.cid || ''
          this.getGoodsList()
        },
        methods: {
          // 获取商品列表数据
          async getGoodsList(cb) {
            // 打开节流阀
            this.isLoading = true
            const {
              data: res
            } = await uni.$http.get('/api/public/v1/goods/search', this.queryObj)
            // 关闭节流阀
            this.isLoading = false
            cb && cb()
            if (res.meta.status !== 200) return uni.$showMsg()
            this.goodsList = [...this.goodsList, ...res.message.goods]
            this.total = res.message.total
          },
          gotoDetail(goods) {
            uni.navigateTo({
              url: '/subpkg/goods_detail/goods_detail?goods_id=' + goods.goods_id
            })
          }
        },
        onReachBottom() {
          if (this.queryObj.pagenum * this.queryObj.pagesize >= this.total) return uni.$showMsg('数据加载完毕')
          if (this.isLoading) return
          // 让页码值自增+1
          this.queryObj.pagenum++
          this.getGoodsList()
        },
        onPullDownRefresh() {
          // 重置关键数据
          this.queryObj.pagenum = 1
          this.total = 0
          this.isLoading = false
          this.goodsList = []
          // 重新发起数据请求
          this.getGoodsList(() => {
            uni.stopPullDownRefresh()
          })
        }
      }
    </script>
    
    <style lang="scss">
    
    </style>

三、自定义商品详情组件 good_detail.vue

<template>
  <view v-if="goods_info.goods_name" class="goods-detail-container">
    <!-- 轮播图区域 -->
    <swiper :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000" :circular="true">
      <swiper-item v-for="(item,i) in goods_info.pics" :key="i">
        <image :src="item.pics_big" @click="preview(i)"></image>
      </swiper-item>
    </swiper>

    <!-- 商品信息区域 -->
    <view class="goods-info-box">
      <!-- 商品价格 -->
      <view class="price">{{goods_info.goods_price}}</view>
      <!-- 商品信息主体区域 -->
      <view class="goods-info-body">
        <!-- 商品名字 -->
        <view class="goods-name">{{goods_info.goods_name}}</view>
        <!-- 收藏 -->
        <view class="favi">
          <uni-icons type="star" size="18" color="gray"></uni-icons>
          <text>收藏</text>
        </view>
      </view>
      <!-- 运费 -->
      <view class="yf">快递:免运费</view>
    </view>
    <rich-text :nodes="goods_info.goods_introduce"></rich-text>

    <!-- 商品导航组件区域 -->
    <view class="goods_nav">
      <uni-goods-nav :fill="true" :options="options" :buttonGroup="buttonGroup" @click="onClick"
        @buttonClick="buttonClick" />
    </view>
  </view>
</template>

<script>
  export default {
	
    watch: {
      total: {
        handler(newVal) {
          const findResult = this.options.find(x => x.text === '购物车')
          if (findResult) {
            findResult.info = newVal
          }
        },
        immediate: true
      }
    },
    data() {
      return {
        goods_info: {},
        options: [{
          icon: 'shop',
          text: '店铺',
          infoBackgroundColor: '#007aff',
          infoColor: "red"
        }, {
          icon: 'cart',
          text: '购物车',
          info: 0
        }],
        buttonGroup: [{
            text: '加入购物车',
            backgroundColor: '#ff0000',
            color: '#fff'
          },
          {
            text: '立即购买',
            backgroundColor: '#ffa200',
            color: '#fff'
          }
        ],
      }
    },
    onLoad(options) {
      const goods_id = options.goods_id
      this.getGoodsDetail(goods_id)
    },
    methods: {
      async getGoodsDetail(goods_id) {
        const {
          data: res
        } = await uni.$http.get('/api/public/v1/goods/detail', {
          goods_id
        })
        if (res.meta.status !== 200) return uni.$showMsg()
        res.message.goods_introduce = res.message.goods_introduce.replace(/<img /g,
            '<img style="display:block;" ')
          .replace(/webp/g, 'jpg')
        this.goods_info = res.message
      },
      preview(i) {
        uni.previewImage({
          current: i,
          urls: this.goods_info.pics.map(x => x.pics_big)
        })
      },
      onClick(e) {
        if (e.content.text === '购物车') {
          uni.switchTab({
            url: '/pages/cart/cart'
          })
        }
      },
      buttonClick(e) {
        if (e.content.text === '加入购物车') {
          // 组织商品的信息对象
          // 每个商品的信息对象,都包含如下 6 个属性:
          // { goods_id, goods_name, goods_price, goods_count, goods_small_logo, goods_state }
          const goods = {
            goods_id: this.goods_info.goods_id,
            goods_name: this.goods_info.goods_name,
            goods_price: this.goods_info.goods_price,
            goods_count: 1,
            goods_small_logo: this.goods_info.goods_small_logo,
            goods_state: true
          }
          // 调用 addToCart 方法
          // this.addToCart(goods)
        }
      }
    }
  }
</script>

<style lang="scss">
  swiper {
    height: 750rpx;

    image {
      width: 100%;
      height: 100%;
    }
  }

  .goods-info-box {
    padding: 10px;
    padding-right: 0;

    .price {
      color: #c00000;
      font-size: 18px;
      margin: 10px 0;
    }

    .goods-info-body {
      display: flex;
      justify-content: space-between;

      .goods-name {
        font-size: 13px;
        margin-right: 10px;
      }

      .favi {
        width: 120px;
        font-size: 12px;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        border-left: 1px solid #eaeaea;
        color: gray;
      }
    }

    .yf {
      font-size: 12px;
      color: gray;
      margin: 10px 0;
    }

  }

  .goods_nav {
    position: fixed;
    bottom: 0;
    left: 0;
    width: 100%;
  }

  .goods-detail-container {
    padding-bottom: 50px;
  }
</style>

四、自定义搜索组件 my_search.vue

<template>
  <view class="my-search-container" :style="{'background-color': bgcolor}" @click="searchBoxHandler">
    <view class="my-search-box" :style="{'border-radius': radius + 'px'}">
      <uni-icons type="search" size="17"></uni-icons>
      <text class="placeholder">搜索</text>
    </view>
  </view>
</template>

<script>
  export default {
    name: "my-search",
    props: {
      // 背景颜色
      bgcolor: {
        type: String,
        default: '#c00000'
      },
      // 圆角尺寸
      radius: {
        type: Number,
        default: 18 //px
      }
    },
    data() {
      return {

      };
    },
    methods: {
      searchBoxHandler() {
        this.$emit('click')
      }
    }
  }
</script>

<style lang="scss">
  .my-search-container {
    height: 50px;
    // background-color: #c00000;
    display: flex;
    align-items: center;
    padding: 0 10px;

    .my-search-box {
      width: 100%;
      height: 36px;
      background-color: #fff;
      // border-radius: 18px;
      display: flex;
      justify-content: center;
      align-items: center;

      .placeholder {
        font-size: 15px;
        margin-left: 5px;
      }
    }
  }
</style>

五、小程序首页 index.vue

<template>
	<view>
		 <!-- 搜索组件 -->
		<view class="search-box">
		 <my-search @click="gotoSearch"></my-search>
		  <!-- 动态给子组件传颜色和圆角像素值 -->
		  <!-- <my-search @click="gotoSearch" :bgcolor="'black'" :radius="18"></my-search> -->
		</view>
		<!-- 轮播图区域 -->
		<swiper :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000" :circular="true">
		  <swiper-item v-for="(item,i) in swiperList" :key="i">
			<navigator class="swiper-item" :url="'/subpkg/goods_detail/goods_detail?goods_id=' + item.good_id">
			  <image :src="item.image_src"></image>
			</navigator>
		  </swiper-item>
		</swiper>
		 <!-- 分类导航区域 -->
		<view class="nav-list">
		  <view class="nav-item" v-for="(item,i) in navList" :key="i" @click="navClickHandler(item)">
			<image :src="item.image_src" class="nav-img"></image>
		  </view>
		</view>
		<!-- 楼层区域 -->
		<view class="floor-list">
		  <!-- 每个楼层的 item 项 -->
		  <view class="floor-item" v-for="(item,i) in floorList" :key="i">
			<!-- 楼层的标题 -->
			<image :src="item.floor_title.image_src" class="floor-title"></image>
			<!-- 楼层的图片区域 -->
			<view class="floor-img-box">
			  <!-- 左侧图片 -->
			  <navigator class="left-img-box" :url="item.product_list[0].url">
				<image :src="item.product_list[0].image_src" :style="{width: item.product_list[0].image_width + 'rpx'}"
				  mode="widthFix">
				</image>
			  </navigator>
			  <!-- 右侧图片 -->
			  <view class="right-img-box">
				<navigator class="right-img-item" v-for="(item2,i2) in item.product_list" :key="i2" v-if="i2 !== 0"
				  :url="item2.url">
				  <image :src="item2.image_src" :style="{width: item2.image_width + 'rpx'}" mode="widthFix"></image>
				</navigator>
			  </view>
			</view>
		  </view>
		</view>
		
	</view>
</template>

<script>
	import {mySearch} from '@/components/my-search/my-search.vue'
	export default {
		components:{mySearch},
		data() {
			return {
				title: 'Hello',
				 // 轮播图数据列表
				swiperList: [],
				// 分类导航的数据列表
				navList: [],
				// 楼层的数据
				floorList: []
			}
		},
		onLoad() {
			this.getSwiperList()
			this.getNavList()
			this.getFloorList()
		},
		methods: {
			gotoSearch() {
				uni.navigateTo({
				  url: '/subpkg/search/search'
				})
		    },
		    async getSwiperList() {
			   const {
				 data: res
			   } = await uni.$http.get('/api/public/v1/home/swiperdata')
			   // 请求失败
			   if (res.meta.status !== 200) return uni.$showMsg()
			   // 请求成功
			   this.swiperList = res.message
			 },
			 async getNavList() {
				 const {
				   data: res
				 } = await uni.$http.get('/api/public/v1/home/catitems')
				 // 请求失败
				 if (res.meta.status !== 200) return uni.$showMsg()
				 // 请求成功
				 this.navList = res.message
			},
			navClickHandler(item) {
				if (item.name === '分类') {
				  uni.switchTab({
					url: '/pages/category/category'
				  })
				}
			},
			async getFloorList() {
				const {
				  data: res
				} = await uni.$http.get('/api/public/v1/home/floordata')
				// 请求失败
				if (res.meta.status !== 200) return uni.$showMsg()
				// 请求成功
				// 对每张图片的 navigator_url 数据进行处理
				res.message.forEach(floor => {
				  floor.product_list.forEach(prod => {
					prod.url = '/subpkg/goods_list/goods_list?' + prod.navigator_url.split('?')[1]
				  })
				})
				this.floorList = res.message
			},
		}
	}
</script>

<style>
	swiper {
	    height: 330rpx;
	  },
	.swiper-item,
	image {
	  width: 100%;
	  height: 100%;
	},
	
	
	.nav-list {
	    display: flex;
	    justify-content: space-around;
	    margin: 15px 0;
	  }
	.nav-img {
	  width: 128rpx;
	  height: 140rpx;
	}
	
	.floor-title {
	    width: 100%;
	    height: 60rpx;
	  }
	
	  .floor-img-box {
	    display: flex;
	    padding-left: 10rpx;
	  }
	
	  .right-img-box {
	    display: flex;
	    flex-wrap: wrap;
	    justify-content: space-around;
	  }
	 
	
 .search-box {
    position: sticky;
    top: 0;
    z-index: 999;
  }
</style>

六、状态管理相关 store(以下文件分别为:store.js cart.js user.js)

import Vue from 'vue'
import Vuex from 'vuex'
import moduleCart from '@/store/cart.js'
import moduleUser from '@/store/user.js'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    'm_cart': moduleCart,
    'm_user': moduleUser
  }
})

export default store
export default {
  namespaced: true,
  state: () => ({
    // 购物车的数组,用来存储购物车中每个商品的信息对象
    // 每个商品的信息对象,都包含如下 6 个属性:
    // { goodsId, goodsName, goodsPrice, goodsCount, goodsSmallLogo, goodsState }
    cart: JSON.parse(uni.getStorageSync('cart') || '[]')
  }),
  mutations: {
    addToCart(state, goods) {
      const findResult = state.cart.find(x => x.goodsId === goods.goodsId)
      if (!findResult) {
        state.cart.push(goods)
      } else {
        findResult.goods_count++
      }
      this.commit('m_cart/saveToStorage')
    },
    saveToStorage(state) {
      uni.setStorageSync('cart', JSON.stringify(state.cart))
    },
    // 更新购物车商品的勾选状态
    updateGoodsState(state, goods) {
      const findResult = state.cart.find(x => x.goodsId === goods.goodsId)
      if (findResult) {
        findResult.goodsState = goods.goodsState
        this.commit('m_cart/saveToStorage')
      }
    },
    updateGoodsCount(state, goods) {
      const findResult = state.cart.find(x => x.goodsId === goods.goodsId)
      if (findResult) {
        findResult.goodsCount = goods.goodsCount
        this.commit('m_cart/saveToStorage')
      }
    },
    // 根据 id 删除对应的商品
    removeGoodsById(state, goods_id) {
      state.cart = state.cart.filter(x => x.goodsId !== goodsId)
      this.commit('m_cart/saveToStorage')
    },
    // 更新购物车中所有的商品勾选状态
    updateAllGoodsState(state, newState) {
      state.cart.forEach(x => x.goodsState = newState)
      this.commit('m_cart/saveToStorage')
    }
  },
  getters: {
    // 购物车中所有商品的总数量
    total(state) {
      // let c = 0
      // state.cart.forEach(goods => c += goods.goods_count)
      // return c
      return state.cart.reduce((total, item) => total += item.goodsCount, 0)
    },
    // 购物车中已勾选的商品的总数量
    checkedCount(state) {
      return state.cart.filter(x => x.goodsState).reduce((total, item) => total += item.goodsCount, 0)
    },
    // 已勾选的商品的总价格
    checkedGoodsAmount(state) {
      return state.cart.filter(x => x.goodsState).reduce((total, item) => total += item.goodsCount * item.goodsPrice,
        0).toFixed(2)
    }
  }
}
export default {
  // 开启命名空间
  namespaced: true,

  // 数据
  state: () => ({
    address: JSON.parse(uni.getStorageSync('address') || '{}'),
    token: uni.getStorageSync('token') || '',
    // 用户的信息对象
    userinfo: JSON.parse(uni.getStorageSync('userinfo') || '{}')
  }),
  mutations: {
    // 更新收货地址
    updateAddress(state, address) {
      state.address = address

      this.commit('m_user/saveAddressToStorage')
    },
    // 持久化存储 address
    saveAddressToStorage(state) {
      uni.setStorageSync('address', JSON.stringify(state.address))
    },
    updateUserInfo(state, userinfo) {
      state.userinfo = userinfo

      this.commit('m_user/saveUserInfoToStorage')
    },
    saveUserInfoToStorage(state) {
      uni.setStorageSync('userinfo', JSON.stringify(state.userinfo))
    },
    updateToken(state, token) {
      state.token = token
      this.commit('m_user/saveTokenToStorage')
    },
    saveTokenToStorage(state) {
      uni.setStorageSync('token', state.token)
    }
  },
  getters: {
    // 收货地址
    addstr(state) {
      if (!state.address.provinceName) return ''
      return state.address.provinceName + state.address.cityName + state.address.countyName + state.address.detailInfo
    }
  }
}

七、main.js

import App from './App'

import store from '@/store/store.js'
// 导入网络请求的包
import { $http } from '@/node_modules/@escook/request-miniprogram'
uni.$http = $http

// 请求根路径
$http.baseUrl = 'https://api-hmugo-web.itheima.net'
// 请求拦截器
$http.beforeRequest = function(options) {
  // 显示loading效果
  uni.showLoading({
    title: '数据加载中...',
  })
  // 判断当前请求的是否为有权限的接口
  if (options.url.indexOf('/my/') !== -1) {
    options.header = {
      Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIzLCJpYXQiOjE1NjQ3MzAwNzksImV4cCI6MTAwMTU2NDczMDA3OH0.YPt-XeLnjV-_1ITaXGY2FhxmCe4NvXuRnRB8OMCfnPo"
    }
  }
}
// 响应拦截器
$http.afterRequest = function() {
  // 隐藏loading效果
  uni.hideLoading()
}
// 封装数据请求失败的弹框方法
uni.$showMsg = function(title = '数据请求失败', duration = 1500) {
  uni.showToast({
    title,
    duration,
    icon: 'none',
  })
}
Vue.config.productionTip = false




import uView from '@/uni_modules/uview-ui'
Vue.use(uView)

// #ifndef VUE3
import Vue from 'vue'
Vue.config.productionTip = false
App.mpType = 'app'

try {
  function isPromise(obj) {
    return (
      !!obj &&
      (typeof obj === "object" || typeof obj === "function") &&
      typeof obj.then === "function"
    );
  }

  // 统一 vue2 API Promise 化返回格式与 vue3 保持一致
  uni.addInterceptor({
    returnValue(res) {
      if (!isPromise(res)) {
        return res;
      }
      return new Promise((resolve, reject) => {
        res.then((res) => {
          if (res[0]) {
            reject(res[0]);
          } else {
            resolve(res[1]);
          }
        });
      });
    },
  });
} catch (error) { }

const app = new Vue({
  ...App
})
app.$mount()
// #endif

// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
  const app = createSSRApp(App)
  return {
    app
  }
}
// #endif

八、引入/uview组件(分别该四个文件:app.vue man.js page.json uni.scss)

<style lang="scss">
	/*每个页面公共css */
	@import "@/uni_modules/uview-ui/index.scss";
</style>
import uView from '@/uni_modules/uview-ui'
Vue.use(uView)
"easycom": {
		"^u-(.*)": "@/uni_modules/uview-ui/components/u-$1/u-$1.vue"
	},
@import '@/uni_modules/uview-ui/theme.scss';

九、uniapp小程序分包
①page.json中定义分包文件
②定义分包文件夹并创建分包文件

"subPackages": [
		{
			"root": "subpkg",
			"pages": [
				{
					"path": "search/search"
				},{
                    "path" : "goods_list/goods_list"
                }
                ,{
                    "path" : "goods_detail/goods_detail",
                    "style" :                                                                                    
                {
                    "navigationBarTitleText": "",
                    "enablePullDownRefresh": false
                }
                
                }
            ]
		}
	],