一 VS Code 开发 uni-app 项目准备

1.1 为什么选择 VS Code ?

  • HbuilderX 对 TS (TypeScript)类型支持暂不完善
  • VS Code 对 TS 类型支持友好,熟悉的编辑器

tips:TypeScript 是 JavaScript 的超集,它在 JavaScript 的基础上添加了强类型、接口、类、泛型等特性,并提供了静态类型检查等工具,让开发者能够在编写代码时更加安全、高效、可靠。与 JavaScript 相比,TypeScript 具有更强的类型系统、更严格的类型检查、更好的代码可读性和维护性等优点。此外,TypeScript 的社区活跃度也非常高,它被越来越多的开发者和公司所采用。

在这里插入图片描述

在这里插入图片描述

1.2 安装VS Code插件

安装三个uni-app相关插件

uni-app插件作用:

  1. 快速创建页面或组件
  2. 代码提示
  3. 鼠标悬停查文档

使用vscode打开我们上一章节创建好的项目

在这里插入图片描述

uni-create-view插件

先安装第一个插件uni-create-view,该插件可以快速创建 uniapp 视图与组件!
在这里插入图片描述

安装后需要进行设置

在这里插入图片描述

按照下边的进行选择

在这里插入图片描述

可以看到,多出来很多功能

在这里插入图片描述

先对vscode进行一下设置

在这里插入图片描述

取消勾选

在这里插入图片描述

此时,在page文件夹上右键,选择新建uniapp页面

在这里插入图片描述

输入框里,前边是页面名称,后边空格,空格后是导航标题。我们输入my 我的,回车

在这里插入图片描述

看效果
在这里插入图片描述

因为我们开发的是小程序,所以我们把div改为view

在这里插入图片描述

可以看到,已经自动注册了页面的路由

在这里插入图片描述

uni-helper插件

主要用来代码提示、代码校验等

在这里插入图片描述

安装后,发现,其实是相当于安装了五个扩展包,各有各的作用

在这里插入图片描述

安装后,鼠标悬停在代码上时是有提示的

在这里插入图片描述

uniapp小程序扩展插件

用来完善文档提示功能

在这里插入图片描述

安装后,鼠标悬停在代码块上后,会有文档提示

在这里插入图片描述

1.3 ts类型校验

安装类型声明文件

pnpm i -D @types/wechat-miniprogram @uni-helper/uni-app-types

在这里插入图片描述

配置tsconfig.json

在这里插入图片描述

此时,代码里类型不对的话就会有提示了

在这里插入图片描述

cscode里使用代码提示快捷键ctrl+i,查看这里有哪些值可以写

在这里插入图片描述

1.4 json注释问题

我们看到package.json里的注释一直报错,因为vscode是严格检查json格式的,只要不是json格式就报错

在这里插入图片描述

解决:打开设置

在这里插入图片描述

搜索“文件关联”,点击“添加项”

在这里插入图片描述

前边写文件名,后边写jsonc(jsonc代表json文件可以加注释)

在这里插入图片描述

然后,继续点击“添加项”

在这里插入图片描述
同样,把pages.json也设置为jsonc即可

在这里插入图片描述

发现,注释已经不报错了

在这里插入图片描述

注意:uniapp里,只有manifest.json和pages.json这俩文件可以写注释,其他json文件不可以写注释。

二 开发uniapp项目

2.1 配置项目基础信息

下载项目基础模板代码:

链接:https://pan.baidu.com/s/1mEejGXG5yBQ4CSvfNVf9Vw
提取码:jmf8

注意:

  1. 在 manifest.json 中添加微信小程序的 appid
  2. 执行先关命令安装依赖、编译为小程序项目 pnpm install 与 pnpm dev:mp-weixin
  3. 导入微信开发者工具里

添加appid

在manifest.json添加微信小程序appid时要注意,文件里最上边有一个appid,不是这个

在这里插入图片描述

而是下边这里,是我们要写的自己的微信小程序appid

在这里插入图片描述

安装依赖

执行pnpm install

在这里插入图片描述

编译为微信小程序pnpm dev:mp-weixin,编译后,在项目目录里看到编译后的dist文件夹

在这里插入图片描述

导入微信开发者工具看实时效果

在这里插入图片描述

选择dist/dev/mp-weixin文件夹

在这里插入图片描述

修改项目名,选择不使用云开发

在这里插入图片描述

效果如下

在这里插入图片描述

项目总共有三大块,分别是构建界面、状态管理、数据交互

2.2 使用 uni-ui 组件库

2.2.1 构建界面

安装uni-ui

官网:https://uniapp.dcloud.net.cn/component/uniui/quickstart.html#npm%E5%AE%89%E8%A3%85

在这里插入图片描述
由于项目里使用pnpm,所以把上边命令的npm改为pnpm

pnpm i @dcloudio/uni-ui 

在这里插入图片描述

组件自动引入

在pages.json里配置如下内容

在这里插入图片描述
如下

在这里插入图片描述

配置好后,需要重启服务

在这里插入图片描述

我们在官网选一个组件,看看能否自动导入

在这里插入图片描述

在项目里粘贴进来,可以看到已经自动导入了相关依赖可以使用该组件了

在这里插入图片描述

配置ts类型

安装依赖

pnpm i -D @uni-helper/uni-ui-types

在 tsconfig.json里进行如下配置

{
	"compilerOptions": {
		"types": [
			"@dcloudio/types",
			"@types/wechat-miniprogram",
			"@uni-helper/uni-app-types",
			 "@uni-helper/uni-ui-types"
		]
	},
}

在这里插入图片描述

2.2.2 状态管理

小程序端 Pinia 持久化

官网:https://prazdevs.github.io/pinia-plugin-persistedstate/guide/config.html#storage

之前网页端持久化,我们使用的api是

  • localStorage.setItem()
  • localStorage.getItem()

兼容多端的api

  • uni.setStorageSync()
  • uni.getStorageSync()

使用方式

src/store/index.ts

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'

src/main.ts

在这里插入图片描述

在src/store/modules/member.ts(会员信息)里使用pania对数据进行持久化

在这里插入图片描述

点击“保存用户信息”,可以看到storage里存进了用户信息

在这里插入图片描述
点击“清理用户信息”

在这里插入图片描述

2.2.3 数据交互

这里讲解请求工具的封装,分为拦截器封装和请求函数的封装

拦截器封装

在这里插入图片描述

其中

我们在项目里添加拦截器,打开src/utils/http.ts文件

// 添加拦截器:
//     拦截 request 请求
//     拦截 uploadFile 文件上传


//TODO:
//1.非 http 开头需拼接地址
//2.请求超时
//3.添加小程序端请求头标识
//4.添加 token 请求头标识

const baseURL = 'https://pcapi-xiaotuxian-front-devtest.itheima.net'

// 添加拦截器
const httpInterceptor = {
  // 拦截前触发
  invoke(options: UniApp.RequestOptions) {
    // 1. 非 http 开头需拼接地址
    if (!options.url.startsWith('http')) {
      options.url = baseURL + options.url
     
    }
    // 2. 请求超时, 默认 60s
    options.timeout = 10000
    console.log(options)
    
  },
}
uni.addInterceptor('request', httpInterceptor)
uni.addInterceptor('uploadFile', httpInterceptor)

在页面添加按钮以及函数

在这里插入图片描述

点击按钮测试,查看控制台

在这里插入图片描述

注意,此时不生效的话,可能需要重新编译启动项目pnpm dev:mp-weixin

也可以看到后端返回了数据

在这里插入图片描述

我们继续在src/utils/http.ts里完成第三步和第四步

在这里插入图片描述
我们在页面里写死一个token

在这里插入图片描述

上边的拦截器只是对请求前做了拦截,并没有对响应做拦截,下面通过请求函数的封装,可以实现对响应的拦截

请求函数封装

在这里插入图片描述

src\utils\http.ts

/**
 * 请求函数
 * @param  UniApp.RequestOptions
 * @returns Promise
 *  1. 返回 Promise 对象
 *  2. 获取数据成功
 *    2.1 提取核心数据 res.data
 *    2.2 添加类型,支持泛型
 *  3. 获取数据失败
 *    3.1 401错误  -> 清理用户信息,跳转到登录页
 *    3.2 其他错误 -> 根据后端错误信息轻提示
 *    3.3 网络错误 -> 提示用户换网络
 */
type Data<T> = {
    code: string
    msg: string
    result: T
  }
  // 2.2 添加类型,支持泛型
  export const http = <T>(options: UniApp.RequestOptions) => {
    // 1. 返回 Promise 对象(resolve成功,reject失败)
    return new Promise ((resolve, reject) => {
      uni.request({
        ...options,
        // 响应成功
        success(res) {
            //提取核心数据 res.data
            resolve(res.data)
        }
      })
    })
  }

页面里,替换之前的request请求方式

在这里插入图片描述

查看日志

在这里插入图片描述

完善http.ts文件,处理请求失败的情况

type Data<T> = {
    code: string
    msg: string
    result: T
  }
  // 2.2 添加类型,支持泛型
  export const http = <T>(options: UniApp.RequestOptions) => {
    // 1. 返回 Promise 对象(resolve成功,reject失败)
    return new Promise<Data<T>>((resolve, reject) => {
      uni.request({
        ...options,
        // 响应成功
        success(res) {
          // 状态码 2xx, axios 就是这样设计的
          if (res.statusCode >= 200 && res.statusCode < 300) {
            // 2.1 提取核心数据 res.data
            resolve(res.data as Data<T>)
          } else if (res.statusCode === 401) {
            // 401错误  -> 清理用户信息,跳转到登录页
            const memberStore = useMemberStore()
            memberStore.clearProfile()
            //跳转到登录页
            uni.navigateTo({ url: '/pages/login/login' })
            reject(res)
          } else {
            // 其他错误 -> 根据后端错误信息轻提示
            uni.showToast({
              icon: 'none',
              title: (res.data as Data<T>).msg || '请求错误',
            })
            reject(res)
          }
        },
        // 响应失败
        fail(err) {
          uni.showToast({
            icon: 'none',
            title: '网络错误,换个网络试试',
          })
          reject(err)
        },
      })
    })
  }

小程需开发工具模拟切换网络

在这里插入图片描述

2.3 首页 – 自定义导航栏

步骤

  1. 准备组件
  2. 隐藏默认导航栏,修改文字颜色
  3. 样式适配 -> 安全区域(针对不同手机上边的刘海,做对应的样式适配)

在这里插入图片描述

静态结构

新建业务组件:src/pages/index/componets/CustomNavbar.vue

<script setup lang="ts">
//
</script>

<template>
  <view class="navbar">
    <!-- logo文字 -->
    <view class="logo">
      <image class="logo-image" src="@/static/images/logo.png"></image>
      <text class="logo-text">新鲜 · 亲民 · 快捷</text>
    </view>
    <!-- 搜索条 -->
    <view class="search">
      <text class="icon-search">搜索商品</text>
      <text class="icon-scan"></text>
    </view>
  </view>
</template>

<style lang="scss">
/* 自定义导航条 */
.navbar {
  background-image: url(@/static/images/navigator_bg.png);
  background-size: cover;
  position: relative;
  display: flex;
  flex-direction: column;
  padding-top: 20px;
  .logo {
    display: flex;
    align-items: center;
    height: 64rpx;
    padding-left: 30rpx;
    padding-top: 20rpx;
    .logo-image {
      width: 166rpx;
      height: 39rpx;
    }
    .logo-text {
      flex: 1;
      line-height: 28rpx;
      color: #fff;
      margin: 2rpx 0 0 20rpx;
      padding-left: 20rpx;
      border-left: 1rpx solid #fff;
      font-size: 26rpx;
    }
  }
  .search {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 10rpx 0 26rpx;
    height: 64rpx;
    margin: 16rpx 20rpx;
    color: #fff;
    font-size: 28rpx;
    border-radius: 32rpx;
    background-color: rgba(255, 255, 255, 0.5);
  }
  .icon-search {
    &::before {
      margin-right: 10rpx;
    }
  }
  .icon-scan {
    font-size: 30rpx;
    padding: 15rpx;
  }
}
</style>

在首页src\pages\index\index.vue里引用上边组件

在这里插入图片描述

tips:这里有报错提示,但是不影响运行,是因为Vetur 暂不支持ts语法,我们卸载vscode的Vetur插件,安装Volar 即可

在这里插入图片描述

在这里插入图片描述

可以看到,我们自定义导航栏和默认导航栏重复了,需要去掉默认导航栏,在pages.json里进行如下配置

在这里插入图片描述

此时,我们切换模拟器机型,选择iphone15pro max

在这里插入图片描述

查看效果

在这里插入图片描述

发现,有的字体被手机刘海挡住了

安全区域

在这里插入图片描述

不同手机的安全区域不同,适配安全区域能防止页面重要内容被遮挡。

可通过 uni.getSystemInfoSync() 获取屏幕边界(上下左右)到安全区的距离。在导航栏代码里打印屏幕到边界距离

在这里插入图片描述

//获取屏幕边界(上下左右)到安全区的距离
const { safeAreaInsets} = uni.getSystemInfoSync()
console.log('边界',safeAreaInsets)

查看控制台

在这里插入图片描述

我们关注的是top这个值(底部的话,微信已经帮我们处理好了,不需要关心),这里top是54,代表屏幕最上边到安全区域距离就是54

在这里插入图片描述

此时,就需要我们动态调整样式来适应不同屏幕top距离

在这里插入图片描述

:style="{paddingTop: safeAreaInsets?.top+'px'}"

此时,切换模拟器的任何机型,都可以完美适配

2.4 通用轮播组件

项目中总共有两处广告位,分别位于【首页】和【商品分类页】。

轮播图组件需要在首页和分类页使用,需要封装成通用组件。

在这里插入图片描述

静态结构

首页广告布局为独立的组件 XtxSwiper ,我们写在 src/components 目录中。

该组件定义了 list 属性接收外部传入的数据,内部通过小程序内置组件 swiper 展示首页广告的数据。

轮播图组件

静态结构:src/components/XtxSwiper.vue

<script setup lang="ts">
import { ref } from 'vue'

const activeIndex = ref(0)
</script>

<template>
  <view class="carousel">
    <swiper :circular="true" :autoplay="false" :interval="3000">
      <swiper-item>
        <navigator url="/pages/index/index" hover-class="none" class="navigator">
          <image
            mode="aspectFill"
            class="image"
            src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/slider_1.jpg"
          ></image>
        </navigator>
      </swiper-item>
      <swiper-item>
        <navigator url="/pages/index/index" hover-class="none" class="navigator">
          <image
            mode="aspectFill"
            class="image"
            src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/slider_2.jpg"
          ></image>
        </navigator>
      </swiper-item>
      <swiper-item>
        <navigator url="/pages/index/index" hover-class="none" class="navigator">
          <image
            mode="aspectFill"
            class="image"
            src="https://pcapi-xiaotuxian-front-devtest.itheima.net/miniapp/uploads/slider_3.jpg"
          ></image>
        </navigator>
      </swiper-item>
    </swiper>
    <!-- 指示点 -->
    <view class="indicator">
      <text
        v-for="(item, index) in 3"
        :key="item"
        class="dot"
        :class="{ active: index === activeIndex }"
      ></text>
    </view>
  </view>
</template>

<style lang="scss">
:host {
  display: block;
  height: 280rpx;
}
/* 轮播图 */
.carousel {
  height: 100%;
  position: relative;
  overflow: hidden;
  transform: translateY(0);
  background-color: #efefef;
  .indicator {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 16rpx;
    display: flex;
    justify-content: center;
    .dot {
      width: 30rpx;
      height: 6rpx;
      margin: 0 8rpx;
      border-radius: 6rpx;
      background-color: rgba(255, 255, 255, 0.4);
    }
    .active {
      background-color: #fff;
    }
  }
  .navigator,
  .image {
    width: 100%;
    height: 100%;
  }
}
</style>

自动导入全局组件

配置后,在首页和分类页直接引用上边的组件即可

参考配置pages.json

{
  // 组件自动引入规则
  "easycom": {
    // 是否开启自动扫描 @/components/$1/$1.vue 组件
    "autoscan": true,
    // 以正则方式自定义组件匹配规则
    "custom": {
      // uni-ui 规则如下配置
      "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
      // 以 Xtx 开头的组件,在 components 目录中查找(需要重启服务才能生效)
      "^Xtx(.*)": "@/components/Xtx$1.vue"
    }
  }
}

在这里插入图片描述
在src/index/index.vue首页里引入轮播图公共组件

在这里插入图片描述
注意,此时,鼠标放在我们自定义组件上后,显示的是unknow

在这里插入图片描述

全局组件类型声明

为全局组件提供类型声明,解决上边unknow的问题

Volar 插件说明:https://github.com/vuejs/language-tools

// src/types/components.d.ts
import XtxSwiper from './XtxSwiper.vue'
declare module 'vue' {
  export interface GlobalComponents {
    XtxSwiper: typeof XtxSwiper
  }
}

danger 版本升级:新版 Volardeclare module '@vue/runtime-core' 调整为 declare module 'vue'

此时,unknow问题解决

在这里插入图片描述

首页 – 轮播图指示点

此时,滑动轮播图的时候,下边的指示点是不变的

在这里插入图片描述

鼠标放swiper上,打开官网

在这里插入图片描述
官网:
在这里插入图片描述

知识点:

  1. UniHelper 提供事件类型,文档说明
  2. ?(可选链) 允许前面表达式为空值
  3. !(非空断言) 主观上排除掉空值情况

在这里插入图片描述

在这里插入图片描述

此时,轮播图下的指示点就会跟着变化了

在这里插入图片描述

首页 – 获取轮播图后台数据

  1. 封装获取轮播图数据API
  2. 页面初始化调用API

接口调用

该业务功能对于前端来说比较简单,只需调用后端提供的接口将获得的数据展现,结合运营人员的营销策略跳转到对应的链接地址即可。

接口地址:/home/banner

请求方式:GET

请求参数:

Query:

字段名 必须 默认值 备注
distributionSite 1 活动 banner 位置,1 代表首页,2 代表商品分类页,默认为 1
// 存放路径: src/services/home.ts
import type { BannerItem } from '@/types/home'
import { http } from '@/utils/http'
/**
 * 首页-广告区域-小程序
 * @param distributionSite 广告区域展示位置(投放位置 投放位置,1为首页,2为分类商品页) 默认是1
 */
export const getHomeBannerAPI = (distributionSite = 1) => {
  return http<BannerItem[]>({
    method: 'GET',
    url: '/home/banner',
    data: {
      distributionSite,
    },
  })
}

首页src\pages\index\index.vue

在这里插入图片描述

控制台可以看到,后台数据已经有了

在这里插入图片描述
接下来就需要把数据渲染到页面里展示出来了

步骤

  1. 定义轮播图数据类型
  2. 指定类型并传值给子组件
  3. 渲染轮播图数据

数据类型声明

存放路径:src/types/home.d.ts,表示首页的接口对应的数据类型声明文件

/** 首页-广告区域数据类型,对应后台接口返回的数据类型 */
export type BannerItem = {
  /** 跳转链接 */
  hrefUrl: string
  /** id */
  id: string
  /** 图片链接 */
  imgUrl: string
  /** 跳转类型 */
  type: number
}

至于后台返回的code、msg、result等通用参数,之前在通用拦截器http.ts里已经定义好了

在这里插入图片描述

最后,将获得的数据结合模板语法渲染到页面中

home.ts

import type { BannerItem } from '@/types/home'
import { http } from '@/utils/http'

/**
 * 首页-广告区域-小程序
 * @param distributionSite 广告区域展示位置(投放位置 投放位置,1为首页,2为分类商品页) 默认是1
 */
export const getHomeBannerAPI = (distributionSite = 1) => {
  return http<BannerItem[]>({
    method: 'GET',
    url: '/home/banner',
    data: {
      distributionSite,
    },
  })
}

页面获取数据src\pages\index\index.vue

在这里插入图片描述

通过自定义属性,把数据传递给子组件<XtxSwiper />

在这里插入图片描述
子组件<XtxSwiper />通过props接收父组件数据

在这里插入图片描述

效果

在这里插入图片描述

轮播图组件代码:src\components\XtxSwiper.vue

<script setup lang="ts">
import type { BannerItem } from '@/types/home';
import { ref } from 'vue'

const activeIndex = ref(0)

// 当 swiper 下标发生变化时触发
const onChange: UniHelper.SwiperOnChange = (ev) => {
  //!:非空断言,主观上排除空值情况
  activeIndex.value = ev.detail.current
}
//定义props接收父组件数据
const props=defineProps<{
  list:BannerItem[]
}>()
//console.log("子组件数据:",props);

</script>

<template>
  <view class="carousel">
    <swiper :circular="true" :autoplay="false" :interval="3000"
     @change="onChange">
      <swiper-item v-for="item in list" :key="item.id">
        <navigator url="/pages/index/index" hover-class="none" class="navigator">
          <image
            mode="aspectFill"
            class="image"
            :src="item.imgUrl"
          ></image>
        </navigator>
      </swiper-item>
    </swiper>
    <!-- 指示点 -->
    <view class="indicator">
      <text
        v-for="(item, index) in list"
        :key="item.id"
        class="dot"
        :class="{ active: index === activeIndex }"
      ></text>
    </view>
  </view>
</template>

<style lang="scss">
:host {
  display: block;
  height: 280rpx;
}
/* 轮播图 */
.carousel {
  height: 100%;
  position: relative;
  overflow: hidden;
  transform: translateY(0);
  background-color: #efefef;
  .indicator {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 16rpx;
    display: flex;
    justify-content: center;
    .dot {
      width: 30rpx;
      height: 6rpx;
      margin: 0 8rpx;
      border-radius: 6rpx;
      background-color: rgba(255, 255, 255, 0.4);
    }
    .active {
      background-color: #fff;
    }
  }
  .navigator,
  .image {
    width: 100%;
    height: 100%;
  }
}
</style>
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐