uniapp项目之小兔鲜儿小程序商城(一) 项目介绍,技术栈,小程序的基础架构,封装拦截器和请求函数

文章目录

一.项目介绍和前置内容

该项目是一个B2C电商平台项目

1.重要链接

项目演示: http://erabbit.itheima.net/#/
接口文档:https://www.apifox.cn/apidoc/shared/0e6ee326-d646-41bd-9214-29dbf47648fa
项目代码(从这里开始开发):git clone http://git.itcast.cn/heimaqianduan/erabbit-uni-app-vue3-ts.git

2.技术栈
  • vue3.0组合式api
  • vue-cli项目脚手架
  • axios请求接口
  • vue-router单页路由
  • vuex状态管理
  • vuex-persiststate数据持久化
  • normalize.css初始化样式
  • @vueuse/core组合api常用工具库
  • 算法Power Set
  • dayjs日期处理小组件
  • validate表单校验

二.创建uniapp项目

两种方式创建uni-app项目:HBuilderX(都是DCloud公司旗下)和命令行

1.使用HBuilderX创建
  • 创建项目:HBuilderX:创建项目>uniapp>默认模板>vue3
  • 下载插件:工具>插件安装>安装新插件:uni-app(vue3)编译器
    在这里插入图片描述

在这里插入图片描述

  • 在微信开发者工具中运行项目:运行>微信开发者工具>选择路径
    在这里插入图片描述

  • 开启服务端口:前往微信开发者工具>设置>安全服务端口:开启
    在这里插入图片描述

  • 分离窗口并固定:开发者工具只做效果展示,只留该效果窗口即可

2.使用命令行创建
vue create -p dcloudio/uni-preset-vue my-uniapp-project
3.如何使用vscode开发uniapp项目?

vscode的优势是对ts类型的支持比较友好,本项目也将使用vscode进行uniapp开发

step1:把项目拉入vscode,开始下相关插件

uni-create-view插件:快速创建uniapp页面或组件
uni-helper插件:uni-app代码提示
uniapp小程序扩展插件:鼠标悬停查文档

step2:ts类型校验
  • 安装类型声明文件:pnpm i -D @types/wechat-miniprogram @uri-helper/uni-app-types
  • 配置tsconfig.json文件
{
    ......
    "types": [
      "@dcloudio/types",
      "miniprogram-api-typings",
      "@types/wechat-miniprogram",
      "@uni-helper/uni-app-types",
      "@uni-helper/uni-ui-types"
    ]
  },
  "vueCompilerOptions": {
    // experimentalRuntimeMode 已废弃,请升级 Vue - Official 插件至最新版本
    "plugins": [
      "@uni-helper/uni-app-types/volar-plugin"
    ]
  },
step3:设置json文件可以允许注释

vscode设置>搜"文件关联">添加:

    项                值
manifest.json        jsonc
pages.json           jsonc
4.pages.json文件的作用是什么?

配置页面的路由,导航栏,tabBar等页面类信息
*有些类似routes数组

5.示例:在项目中实现一个tabbar功能

把图标在pages/static作为静态资源放在该目录下
实现tabBar步骤:

1.pages右键>新建uniapp页面:my
2.pages/pages.json中新增tabBar配置如下:
"tabBar":{
    "list":[
        {'pagePath':'pages/index/index','text':'首页','iconPath':"xxx.png",'selectedIconPath':'xxx_selected.png'},
        {'pagePath':'pages/my/my','text':'我的'}
    ]
}

3.复制图标文件到statics文件夹中,配置图标路径
6.如何在手机上查看当前项目效果?

manifest.json配置AppID,从微信小程序配置复制
在这里插入图片描述

7.uniapp和原生小程序开发的区别?

每个页面都是vue文件,数据绑定和事件处理使用vue规范

  • 数据绑定:

属性绑定不再需要写成src='{{ url }}',直接写成vue的动态绑定属性:src='url'

  • 事件绑定:

之前:bindtap='eventName'
现在:@tap='eventName',并支持传参

  • 支持vue常用指令

三. 拉取项目代码(直接从这儿开始)

如何拉取项目代码?

项目并非从零开发,直接拉取项目代码:git clone http://git.itcast.cn/heimaqianduan/erabbit-uni-app-vue3-ts.git

拉取项目代码后需要做什么?
  • manifest.json中添加微信小程序的appid
  • 初始化:pnpm install
  • 生成 dist/mp-weixin文件:pnpm dev:mp-weixin
  • 微信开发者工具中导入项目mp-weixin

四.小程序的基础架构

小程序的基础架构具体实现分为:构建页面,状态管理和数据交互

1.构建页面

构建界面三个部分分为:安装uni-ui组件库,自动引入组件,配置ts类型

1.1.安装uni-ui组件库

安装命令:npm i @dcloudio/uni-ui

1.2.让uni-ui组件库中的组件实现自动按需导入
// pages.json
{
  // 组件自动导入
  "easycom": {
    "autoscan": true,
    "custom": {
      // uni-ui 规则如下配置  
      "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue" 
    }
  },
  "pages": [
    // …省略
  ]
}

此时可以直接使用uni-ui的相关组件:

<uni-card>
    .....
</uni-card>
1.3.配置TS类型
  • step1:安装依赖npm i -D @uni-helper/uni-ui-types
  • step2:配置tsconfig.json

确保compilerOptions.types中含有@dcloudio/types@uni-helper/uni-ui-types,且include包含了对应的vue文件

// tsconfig.json
{
  "compilerOptions": {
    // ...
    "types": [
      "@dcloudio/types", // uni-app API 类型
      "miniprogram-api-typings", // 原生微信小程序类型
      "@uni-helper/uni-app-types", // uni-app 组件类型
      "@uni-helper/uni-ui-types" // uni-ui 组件类型  
    ]
  },
  // vue 编译器类型,校验标签类型
  "vueCompilerOptions": {
    "nativeTags": ["block", "component", "template", "slot"]
  }
}
  • step3:重启vscode,成功标志----<uni-card>有了类型声明
2.状态管理(以my页的数据持久化为例)

最终目的是设置小程序端的Pinia持久化

Pinia持久化的方案与之前的写法有些许不同:
以前的持久化主要针对的是网页端的localStorage
而小程序端的持久化针对的是小程序端的api,即:uni.setStorageSync()getsetStorageSync()

step0:安装持久化存储插件pinia-plugin-persistedstate

安装命令:pnpm i pinia-plugin-persistedstate
(Pinia 用法与 Vue3 项目完全一致,uni-app 项目仅需解决持久化插件兼容性问题。)

step1:在stores/index.ts中准备好pinia相关代码
import {createPinia} from "pinia"
import persist from 'pinia-plugin-persistedstate'

//创建pinia实例
const pinia = createPinia()
//使用持久化存储插件
pinia.use(persist)
//默认导出,给main.ts使用
export default pinia
//模块统一导出
export * from "/modules/member"
step2:在main.ts中准备好相关代码
import {createSSRApp} from 'vue'
import App from './App.vue'

//导入pinia实例
import pinia from './stores'

export function createApp(){
    //创建vue实例
    const app=createSSRApp(App)
    //使用pinia
    app.use(pinia)

    return {
        app
    }
}

*若pinia类型报错,解决方法:npm i pinia

step3:在stores/modules/member.ts中提前准备好文件用于管理会员信息
import { defineStore } from 'pinia'
import { ref } from 'vue'


// 定义 Store
export const useMemberStore = defineStore(
  'member',
  () => {
    // 会员信息
    const profile = ref<any>()


    // 保存会员信息,登录时使用
    const setProfile = (val: any) => {
      profile.value = val//用户保存的信息更新到空的profile中
    }


    // 清理会员信息,退出时使用
    const clearProfile = () => {
      profile.value = undefined
    }


    // 记得 return
    return {
      profile,
      setProfile,
      clearProfile,
    }
  },
  // TODO: 持久化
  {
    persist: true,
  },
)
step4:在src/pages/my/my.vue中提前准备好页面和代码
<script setup lang="ts">
import { useMemberStore } from '@/stores'


const memberStore = useMemberStore()
</script>


<template>
  <view class="my">
    <view>会员信息:{{ memberStore.profile }}</view>
    <button
      @tap="
        memberStore.setProfile({
          nickname: '黑马先锋',
        })
      "
      size="mini"
      plain
      type="primary"
    >
      保存用户信息
    </button>
    <button @tap="memberStore.clearProfile()" size="mini" plain type="warn">清理用户信息</button>
  </view>
</template>


<style lang="scss">
//
</style>

当前的代码可以实现数据的添加和清除,但是还没有实现持久化
一刷新页面,数据就会丢失
如何实现?

step5【重点】:改member.ts中的 persist: true语句
  // TODO: 持久化
  {
    persist: true,
  },

该语句只能在网页端实现持久化,要在小程序端实现持久化,要改正如下:

  // TODO: 持久化
  {
    persist: {
        storage:{
            getItem(key){
               return uni.getStorageSync(key)
            },
            setItem(key,value){
               return uni.setStorageSync(key,value)
            }
        }
    }
  },

*注:此处遇到了persist的类型报错问题,后续解决

3.数据交互

封装请求工具:拦截器和请求函数

3.1.封装拦截器

通过拦截器的封装,实现request请求和uploadFile上传文件的拦截
接口文档:https://www.apifox.cn/apidoc/shared/0e6ee326-d646-41bd-9214-29dbf47648fa

uniapp中如何添加一个拦截器(语法)?
uni.addIntercepters(STRING,OBJECT),其中,
	STRING是拦截器的名称,
	OBJECT的参数是传入具体的配置(可以写一个invoke函数,在拦截前触发)
拦截器要做什么?
  • 封装请求基地址
  • 超时时间
  • 添加请求头标识
  • 添加token
如何实现对request请求和上传文件请求的拦截?(重点)
//src/utils/http.ts
import { useMemberStore } from '@/stores'
//基地址
const baseURL = 'http://pcapi-xiaotuxian-front-devtest.itheima.net'
// 添加拦截器:拦截请求和上传文件两个接口
const httpInterceptor = {
  //拦截器对象----拦截前触发的函数,其中options就是请求的配置对象,它会拿到uni.request({methods:xxx,url:'xxx'})中的配置对象
  invoke(options: UniApp.RequestOptions) {
    //指定参数的类型:鼠标悬停在uni.request上获取
    //1.对于非http开头的url,会自动拼接baseURL
    if (!options.url.startsWith('http')) {
      options.url = baseURL + options.url
    }
    //2.请求超时:默认10s
    options.timeout = 10000
    //3.添加小程序端请求头标识
    options.header = {
      ...options.header, //如果存在,就保留原有header
      'source-Client': 'miniapp',
    }
    //4.添加token请求头标识:登录成功后,拿到token,放到这里
    const memberStore = useMemberStore()
    const token = memberStore.profile?.token //此处的token从memberStore,profile中获取
    if (token) {
      options.header.Authorization = token
    }
    return options
  },
}
// 拦截request请求
uni.addInterceptor('request', httpInterceptor)
// 拦截上传文件请求
uni.addInterceptor('uploadFile', httpInterceptor)
验证:请求拦截器是否添加成功
//src/pages/my/my.vue
<script setup lang="ts">
import { useMemberStore } from '@/stores'
const memberStore = useMemberStore()
import '@/utils/http'
// 测试接口按钮
const getData = async () => {
  uni.request({
    url: '/home/banner',
    method: 'GET',
    header: {},
  })
}
</script>
<template>
  <view class="my">
    <view>会员信息:{{ memberStore.profile }}</view>
    <button
      @tap="
        memberStore.setProfile({
          nickname: '黑马先锋',
          token: '1234567890',
        })
      "
      size="mini"
      plain
      type="primary"
    >
      保存用户信息
    </button>
    <button @tap="memberStore.clearProfile()" size="mini" plain type="warn">清理用户信息</button>
    <!-- 新增一个按钮,测试接口请求 -->
    <button @tap="getData" size="mini" plain type="primary">测试接口请求</button>
  </view>
</template>

效果:
在这里插入图片描述
点击"保存用户信息",登录时:
在这里插入图片描述
点击"清理用户信息",退出登录时:
在这里插入图片描述

3.2.封装请求函数

axios的返回值是一个Promise对象,配置async和await可以更方便获取成功的数据.为了方便使用,我们封装的请求函数也要返回一个Promise对象

通过自己封装的请求函数实现之前axios响应拦截器的业务功能,对于响应拦截器,又分为成功和失败两种情况

成功时,
    提取核心数据(res.data)
    添加类型(支持泛型):uni.request函数不支持添加类型,因此要通过函数封装,自己添加泛型,通过泛型来确定后续使用的具体类型
失败时,
    处理网络错误:提示用户更换网络
    401 错误:清理用户信息,跳转登录页
    其他错误:根据后端错误信息轻提示
3.2.1.请求成功的业务处理

在这里插入图片描述

响应拦截器的封装
//http.ts
......
//定义一个后端返回值的类型
interface Data<T> {
  code: string //状态码:'1"
  msg: string //提示信息:'请求成功'
  result: T //核心数据类型:{{}.{},{},{},{}}
}
// 2.给http添加类型(其类型可变)--泛型:T代表任意类型
//T接收到的类型用来确定my.vue中res的类型
export const http = <T>(options: UniApp.RequestOptions) => {
  //给Promise(当前是Unknown)指定响应成功的类型
  return new Promise<Data<T>>((resolve, reject) => {
    uni.request({
      ...options,
      // 请求成功
      success(res) {
        // resolve(res.data) //1.获取核心数据res.data
        //res.data类型报错,应为string|AnyObject|ArrayBuffer中的AnyObject,并准确指定为Data<T>
        //使用类型断言,将res.data的类型断言为Data<T>
        resolve(res.data as Data<T>)
        // 此时可以去my.vue中使用res进行测试
      },
    })
  })
}
验证:请求拦截器是否添加成功
//my.vue
<script setup lang="ts">
import { useMemberStore } from '@/stores'
const memberStore = useMemberStore()
// import '@/utils/http'
import { http } from '@/utils/http'
// 测试接口按钮
const getData = async () => {
  // uni.request({
  //   url: '/home/banner',
  //   method: 'GET',
  //   header: {},
  // })
  const res = await http<string[]>({
    method: 'GET',
    url: '/home/banner',
    header: {},
  })
  console.log('请求成功:', res.code)// 请求成功: 1
}
</script>

结果:
在这里插入图片描述

  • 步骤总结
    首先内部返回一个Promise对象,方便通过asyncawait获取到数据,
    为了使用的数据更加简洁使用类型推断提取出核心数据:res.data as Data<T>
    最后再为TS项目设置类型:(通过泛型实现)
3.2.2.请求失败的业务处理

uni.request的success回调函数仅仅表示服务器响应成功,但未处理状态码,业务中使用不方便

uni.request({
    ...options,
    //响应成功
    success(res){},
    //响应失败
    fail(err){}
})

上述代码一有响应就走success,会带来以下问题:

若服务器有响应,但是响应的结果是token获取失败,此时也会走success回调,
这无疑在逻辑上不准确 
而axios函数仅仅只有响应状态码为2xx时才调用resolve函数,
表示获取数据成功,业务中使用更准确

核心代码如下:

  if (res.statusCode >= 200 && res.statusCode < 300) {
          //请求成功:2xx
          resolve(res.data as Data<T>)
        } else if (res.statusCode === 401) {
          //请求失败:401
          //清理用户信息,跳转登录页面
          uni.showToast({
            // title:'登录过期,请重新登录',
            icon: 'none',
            title: (res.data as Data<T>).msg || '登录过期,请重新登录',
          })
        } else {
          //其他错误:根据后端返回的message提示用户
          uni.showToast({
            title: (res.data as Data<T>).msg || '请求错误',
            icon: 'none',
          })
          reject(new Error((res.data as Data<T>).msg))
        }
3.2.3.http.ts完整代码
import { useMemberStore } from '@/stores'
/* 请求拦截器 */
/*
添加拦截器:拦截请求和上传文件两个接口
TODO:
  1.非http开头的url,会自动拼接baseURL
  2.请求超时
  3.添加小程序端请求头标识
  4.添加token请求头标识
*/

//请求基地址
const baseURL = 'http://pcapi-xiaotuxian-front-devtest.itheima.net'
// 添加拦截器:拦截请求和上传文件两个接口
const httpInterceptor = {
  // 拦截器对象
  //拦截前触发的函数,其中options就是请求的配置对象
  //它会拿到uni.request({methods:xxx,url:'xxx'})中的配置对象
  invoke(options: UniApp.RequestOptions) {
    //指定参数的类型:鼠标悬停在uni.request上获取
    //1.对于非http开头的url,会自动拼接baseURL
    if (!options.url.startsWith('http')) {
      options.url = baseURL + options.url
    }
    //2.请求超时:默认10s
    options.timeout = 10000
    //3.添加小程序端请求头标识
    options.header = {
      ...options.header, //如果存在,就保留原有header
      'source-Client': 'miniapp',
    }
    //4.添加token请求头标识:登录成功后,拿到token,放到这里
    const memberStore = useMemberStore()
    const token = memberStore.profile?.token //此处的token从memberStore,profile中获取
    if (token) {
      options.header.Authorization = token
    }
    return options
  },
}
// 拦截request请求
uni.addInterceptor('request', httpInterceptor)
// 拦截上传文件请求
uni.addInterceptor('uploadFile', httpInterceptor)

/* 响应拦截器 */
/**
 * 请求函数
 * @params UniApp.RequestOptions
 * @returns Promise<any>
 * TODO:
 * 1.返回Promise对象
 * 2.请求成功
 * 2.1.获取核心数据res.data
 * 2.2.添加类型,支持泛型
 * 3.请求失败
 * 3.1.网络错误:提示用户换网络
 * 3.2.401错误:清理用户信息,跳转登录页面
 * 3.3.其他错误:根据后端返回的message提示用户
 */
//定义一个后端返回值的类型
interface Data<T> {
  code: string //状态码:'1"
  msg: string //提示信息:'请求成功'
  result: T //核心数据类型:{{}.{},{},{},{}}
}

// 2.给http添加类型(其类型可变)--泛型:T代表任意类型
//T接收到的类型用来确定my.vue中res的类型
export const http = <T>(options: UniApp.RequestOptions) => {
  //给Promise(当前是Unknown)指定响应成功的类型
  return new Promise<Data<T>>((resolve, reject) => {
    uni.request({
      ...options,
      // 请求成功
      success(res) {
        // resolve(res.data) //1.获取核心数据res.data
        //res.data类型报错,应为string|AnyObject|ArrayBuffer中的AnyObject,并准确指定为Data<T>
        //使用类型断言,将res.data的类型断言为Data<T>
        // resolve(res.data as Data<T>)
        // 此时可以去my.vue中使用res进行测试

        //优化:根据后端返回的状态码,判断请求是否成功
        if (res.statusCode >= 200 && res.statusCode < 300) {
          //请求成功:2xx
          resolve(res.data as Data<T>)
        } else if (res.statusCode === 401) {
          //请求失败:401
          //清理用户信息,跳转登录页面
          uni.showToast({
            // title:'登录过期,请重新登录',
            icon: 'none',
            title: (res.data as Data<T>).msg || '登录过期,请重新登录',
          })
        } else {
          //其他错误:根据后端返回的message提示用户
          uni.showToast({
            title: (res.data as Data<T>).msg || '请求错误',
            icon: 'none',
          })
          reject(new Error((res.data as Data<T>).msg))
        }
      },
      // 请求失败
      fail(err) {
        //提示用户换网络
        uni.showToast({
          title: '请求失败,请检查您的网络',
          icon: 'none',
        })
        reject(err)
      },
    })
  })
}

### 关于“小兔鲜儿微信小程序完整版源码 对于特定名称的小程序如“小兔鲜儿”,虽然存在大量关于不同功能用途的微信小程序开源项目描述[^1],但具体到名为“小兔鲜儿”的项目,在提供的参考资料中并未直接提及。通常情况下,这类项目的获取方式可能包括官方渠道发布、开发者社区共享或是通过教育平台作为案例研究的部分。 如果目标是找到具有相似特性的电商类或服务类微信小程序源码用于学习目的,则可以参考些公开可用的例子来理解其架构技术实现细节。例如: #### 基础结构概述 大多数微信小程序都会遵循定的基础框架搭建模式,这其中包括但不限于页面布局文件(WXML)、样式表(WXSS),以及逻辑处理脚本(JS)。以个典型的商品展示型应用为例,以下是简化后的目录结构与部分核心代码片段: ```plaintext ├── app.js // 应用级配置及全局函数定义 ├── app.json // 页面路径声明其他设置 ├── app.wxss // 全局公共样式 └── pages/ ├── index/ // 首页模块 └── ... ├── goods-detail/ // 商品详情页模板 └── ... ``` #### 示例:首页加载数据请求 (JavaScript) ```javascript // pages/index/index.js Page({ data: { productList: [] }, onLoad() { wx.request({ url: &#39;https://example.com/api/products&#39;, // 替换成实际API地址 method: &#39;GET&#39;, success(res){ this.setData({productList: res.data}); } }); } }) ``` #### 数据库交互说明 考虑到提到的内容涉及到了数据库的存在,这意味着服务器端也需要相应的接口支持前端的数据读取与更新操作。般而言,会采用RESTful API的形式来进行通信,并且后端可以选择Node.js配合Express框架快速构建起所需的服务层。 然而值得注意的是,“小兔鲜儿”作为个具体的商业产品或者个人作品集中的项目名,除非该项目本身已经对外公布了完整的开发资料,否则很难在网络上轻易获得确切版本的源码副本。建议可以通过联系原作者、参与相关技术论坛交流或者是探索GitHub等平台上是否有类似的开源替代方案来进行深入的学习实践。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端OnTheRun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值