铜九铁十,万字总结快速上手uni-app开发(上篇)

403 阅读18分钟

前言

大家好,我是橙海LZ。(好久没发文章了- -)

离职也有一个多月了,最近一周在BOSS直聘和智联招聘投了几天简历,杭州互联网行情相当惨淡,主动沟通了几十家的公司,就约到寥寥几家公司的面试邀请。

在投递过程中发现,很多公司岗位JD上写需要有跨端、可视化、服务端等业务方面的开发经验,在上家公司跨端这块业务做的比较少,所以去B站上找了黑马的uni-app视频快速学习下,这篇文章基于视频内容和官方文档的知识点做一个总结。

文章一共分为两篇,阅读过程中能够了解到:

  1. uni-app常用开发模式
  2. uni-ui组件库使用
  3. 登录流程的基本实现
  4. 发布上线
  5. 原生App打包
  6. uniCloud云开发

准备工作

安装HBuilderX编辑器

安装HBuilderX,在官网下载即可。

第一次安装使用HBuilderX,需要去安装一些必要插件。 1.png

如果是使用HBuilderX进行项目开发,需要在「设置-运行设置」中去配置下微信开发者工具的安装路径(其他平台同理)。

2.png

运行项目后可能会碰到如下报错,去微信开发者工具中的「设置-安全」中开启「服务端口」,

3.png

4.png 注意:开启以后需要重启微信开发者工具。

再次运行项目,并将打包编译好的微信小程序目录导入到微信开发者工具中。

通过VScode进行uni-app开发

推荐安装的vscode插件

5.png

插件安装好后,可以悬停显示文档说明、代码提示、以及快速创建uni-app文件等功能。

6.png

刚开始创建文件,会发现创建页面以后只有index文件,没有创建同名文件夹,这里需要找到图中的扩展修改下配置。

7.png 8.png

以及修改下vscode配置,让manifest.jsonpages.json文件能够编写注释。

注意只用增加这两个文件的配置即可,不用配置如*.json的内容,其他json文件依旧保持原先配置。

image.png

项目开发

拉取项目模板

git clone http://git.itcast.cn/heimaqianduan/erabbit-uni-app-vue3-ts.git heima-shop

manifest.json文件中配置「AppID」 image.png image.png

HBuilderX中打开manifest.json文件,是一个可视化配置的窗口,配置同理。

uni-ui组件库

uni-ui是DCloud提供的一个跨端ui库,它是基于vue组件的、flex布局的、无dom的跨全端ui框架。

uni-ui 不包括内置组件,它是内置组件的补充。

安装

pnpm add @dcloudio/uni-ui

官方文档在介绍uni-ui时,提到了支持easycom模式,它是一种只要符合指定规范,无需import就能使用组件的方式。

注意,配置好easycom后需要重启项目

image.png

easycom模式

image.png

配置TS类型声明

安装相关类型声明,并在tsconfig.json文件中修改配置

pnpm add -D @types/wechat-miniprogram @uni-helper/uni-app-types @uni-helper/uni-ui-types
{
  "extends": "@vue/tsconfig/tsconfig.json",
  "compilerOptions": {
    "allowJs": true,
    "sourceMap": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
    "lib": ["esnext", "dom"],
    "types": [
      "@dcloudio/types",
      "miniprogram-api-typings",
      "@uni-helper/uni-app-types",
      "@uni-helper/uni-ui-types"
    ]
  },
  "vueCompilerOptions": {
    // experimentalRuntimeMode 已废弃
    // "nativeTags": ["block", "component", "template", "slot"]
    // 目前最新的配置方式,是通过plugins属性处理
    "plugins": ["@uni-helper/uni-app-types/volar-plugin"]
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

状态管理

该项目基于Vue3进行开发,状态管理库选择pinia,同时通过pinia-plugin-persistedstate进行持久化存储,和普通web端持久化配置不同,这里需要使用图中用法,否则持久化内容不会生效。

image.png

image.png

image.png

Configuration | Pinia Plugin Persistedstate (prazdevs.github.io)

基于uni.request封装请求实例

通过uni.addInterceptor来添加拦截器,对常见的request请求和uploadFile上传文件场景做请求拦截处理参数信息。

同时在获取服务器响应内容后,针对除200状态码以外的情况做了拦截;401状态下意味着用户尚未登录,强制跳转到登录页;其余情况根据接口返回的信息来抛出提示框,如果没有信息返回默认展示为请求失败。

import { useMemberStore } from '@/stores'

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

const httpInterceptor = {
  invoke(options: UniApp.RequestOptions) {
    if (!options.url.startsWith('http')) {
      options.url = baseURL + options.url
    }

    options.timeout = 10000
    options.header = {
      ...options.header,
      'source-client': 'miniapp',
    }

    const token = useMemberStore().profile?.token
    if (token) {
      options.header.Authorization = token
    }
  },
}

uni.addInterceptor('request', httpInterceptor)
uni.addInterceptor('uploadFile', httpInterceptor)

interface IData<T> {
  code: string
  msg: string
  result: T
}

export const http = <T>(options: UniApp.RequestOptions) => {
  return new Promise<IData<T>>((resolve, reject) => {
    uni.request({
      ...options,
      success(res) {
        if (res.statusCode >= 200 && res.statusCode < 300) {
          resolve(res.data as IData<T>)
        } else if (res.statusCode === 401) {
          useMemberStore().clearProfile()
          uni.navigateTo({
            url: '/pages/login/login',
          })
          reject(res)
        } else {
          uni.showToast({
            title: (res.data as IData<T>).msg || '请求失败',
            icon: 'none',
          })
          reject(res)
        }
      },
      fail(err) {
        uni.showToast({
          title: '请求失败',
          icon: 'none',
        })
        reject(err)
      },
    })
  })
}

请求实例使用

image.png

正常获取到数据

image.png

uni-app更推荐使用内置的请求API(比如将前缀 wxmy 等替换为 uni),因为uni-app集成兼容了各大主流平台的大多数API。

当然如果想使用axios这类的HTTP库,也有适配方案及配置方式,可以自行了解下。

首页模块

自定义导航栏

默认展示的导航栏如图所示 image.png

不过因为展示元素内容相对有限,可以手动实现导航栏来覆盖

<script setup lang="ts">
// 获取屏幕边界到安全区域距离
const { safeAreaInsets } = uni.getSystemInfoSync()
</script>

<template>
  <view class="navbar" :style="{ paddingTop: safeAreaInsets!.top + 10 + 'px' }">
    <!-- 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;
    .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>

这里在使用uni.getSystemInfoSync()API时,uni可能会出现ts报错无法找到情况,如果出现安装在类型声明即可。

pnpm add @types/uni-app -D

将自定义导航栏组件引入首页使用的效果如下

image.png

注意此时引入使用后,可以发现其实是展示了两个导航栏,这里还需要去pages.json文件中配置,来覆盖默认展示的导航栏。

{
    "page": {
        {
            "path": "pages/index/index",
            "style": {
                    "navigationBarTitleText": "首页",
                    "navigationStyle": "custom", // 用于设置导航栏样式为默认还是自定义
                    "navigationBarTextStyle": "white" // 用于设置导航栏标题颜色及状态栏前景颜色,只支持白色或黑色。
            }
        },
    }
}

看下在不同设备下的展示效果

image.png

轮播图

在根目录下的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 接收
defineProps<{
  list: BannerItem[]
}>()
</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">
@import './styles/XtxSwiper.scss';
</style>

pages.json中追加匹配easycom模式。

"easycom": {
    "autoscan": true,
    "custom": {
        "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
        // 追加配置,符合Xtx开头命名的组件无需import,可以直接使用
        "^Xtx(.*)": "@/components/Xtx$1.vue"
    }
},

src/types/component.d.ts配置全局自定义文件的类型声明,用于正确显示自定义组件的ts类型。

import 'vue'
import XtxSwiper from '@/components/XtxSwiper.vue'

declare module 'vue' {
  export interface GlobalComponents {
    XtxSwiper: typeof XtxSwiper
  }
}

将鼠标悬停到组件上,就会正确显示组件类型

image.png

效果如下

20240826133232_rec_.gif

滚动容器scroll-view

<scroll-view>是uni-app提供的内置组件,用于区域滚动。

在首页这里,将除了顶部自定义导航栏以外的内容放置到滚动容器内,并设置flex: 1,让滚动容器填满除了自定义导航栏以外的区域。

image.png

效果如下

1.gif

滚动底部事件

滚动容器可以通过监听scrolltolower事件,来完成滚动到底部去获取数据的常见交互。

  <scroll-view
    scroll-y
    class="scroll-view"
    @scrolltolower="onScrolltolower"
  >
      <XtxSwiper :list="bannerList" />
      <CategoryPanel :list="categoryList" />
      <HotPanel :list="hotList" />
      <XtxGuess ref="XtxGuessRef" />
  </scroll-view>
const XtxGuessRef = ref<XtxGuessInstance>()

const onScrolltolower = () => {
  XtxGuessRef.value?.getMore()
}
// types/component.d.ts
import 'vue'
import XtxSwiper from '@/components/XtxSwiper.vue'
import XtxGuess from '@/components/XtxGuess.vue'

declare module 'vue' {
  export interface GlobalComponents {
    XtxSwiper: typeof XtxSwiper
    XtxGuess: typeof XtxGuess
  }
}

export type XtxGuessInstance = InstanceType<typeof XtxGuess>

getMore方法是子组件通过defineExposeAPI暴露给父组件调用的获取数据方法。

image.png

效果如下

2.gif

下拉刷新

<scroll-view>组件已内置了下拉刷新这部分功能。

  <scroll-view
    scroll-y
    refresher-enabled
    :refresher-triggered="isRefreshFlag"
    class="scroll-view"
    @scrolltolower="onScrolltolower"
    @refresherrefresh="onRefresh"
  >
      <XtxSwiper :list="bannerList" />
      <CategoryPanel :list="categoryList" />
      <HotPanel :list="hotList" />
      <XtxGuess ref="XtxGuessRef" />
  </scroll-view>

refresher-enabled:开启自定义下拉刷新

refresher-triggered:设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发

refresherrefresh:自定义下拉刷新被触发的事件

const onRefresh = async () => {
  // 标记开始刷新,并开启动画
  isRefreshFlag.value = true
  
  // 业务逻辑
  XtxGuessRef.value?.reset()
  await getData()
  await XtxGuessRef.value?.getMore()
  
  // 标记刷新结束,并关闭动画
  isRefreshFlag.value = false
}

image.png

骨架屏

微信开发者工具可以快速根据当前页面结构,生成骨架屏的样式代码。

image.png

image.png

image.png

点击确认后会生成index.skeleton.wxmlindex.skeleton.wxss两个文件。

接下来在VScode中,在首页index目录下,新建components文件夹并新建PageSkeleton.vue文件,将创建的骨架屏文件相关内容拷贝到该.vue文件下。

注意复制代码进来后,还需要删除调整部分代码:

  1. 可以删除顶部备注
  2. 组件属性修改为动态绑定,如scroll-view组件上的refresh-enabled="true"需要修改为:refresh-enabled="true"
  3. css文件内容也拷贝进来
<template>
  <CustomNavbar />
  <scroll-view
    refresher-enabled
    scroll-y
    :refresher-triggered="isRefreshFlag"
    class="scroll-view"
    @scrolltolower="onScrolltolower"
    @refresherrefresh="onRefresh"
  >
    <PageSkeleton v-if="isLoading" />
    <template v-else>
      <XtxSwiper :list="bannerList" />
      <CategoryPanel :list="categoryList" />
      <HotPanel :list="hotList" />
      <XtxGuess ref="XtxGuessRef" />
    </template>
  </scroll-view>
</template>

效果如下

image.png

在uni-app中骨架屏代码的编写还是挺方便的,好评!

热门推荐模块

在首页跳转到这个热门推荐页面中会携带路径参数,uni-app可以通过propsonLoad生命周期方法中的options参数来获取路径参数。

// hot.vue
const props = defineProps<{
    type: string
}>()
console.log(props, '---props')


onLoad((options) => {
    console.log(options, '---options')
})

image.png

// hot.vue
// 这里在onload中根据路径传参去动态修改导航栏标题
onLoad((options) => {
  currUrlMap.value = hotMap.find((item) => item.type === options!.type)!
  uni.setNavigationBarTitle({
    title: currUrlMap.value.title,
  })

  getHotRecommendData()
})

3.gif

使用props的好处是能够在全局文件中的任意位置使用,但是props无论在Vue还是React中大多用于父子组件通信,当获取页面参数也通过props来获取时,感觉比较奇怪。

使用onLoad中的options参数似乎更符合正常需要,但是就只能在onLoad方法内部使用,要想在其他位置使用,要么维护一个变量存储后再在组件内使用。

大家如果有更好的思路/方法,欢迎评论区讨论!

分类模块

页面效果如下

image.png

依旧根据页面内容,通过微信开发者工具生成骨架屏内容

点击图片通过<navigator>组件跳转至商品详情页

<navigator
  v-for="goods in item.goods"
  :key="goods.id"
  class="goods"
  hover-class="none"
  :url="`/pages/goods/goods?id=${goods.id}`"
>
  <image class="image" :src="goods.picture"></image>
  <view class="name ellipsis">{{ goods.name }}</view>
  <view class="price">
    <text class="symbol">¥</text>
    <text class="number">{{ goods.price }}</text>
  </view>
</navigator>

这里点击图片的预览效果,通过uni.previewImage实现

image.png
uni.previewImage({
    current: url, // current 为当前显示图片的链接/索引值,不填或填写的值无效则为 urls 的第一张。
    urls: goods.value!.mainPictures, // 需要预览的图片链接列表
})
image.png

这里使用的是uni-ui组件库中的<uni-popup>弹出层组件,将地址面板组件和服务说明组件做了包裹,让两者均以弹出层的形式展示。

  <uni-popup ref="popup" type="bottom" background-color="#fff">
    <AddressPanel v-if="popupName === 'address'" @close="popup?.close()" />
    <ServicePanel v-if="popupName === 'service'" @close="popup?.close()" />
  </uni-popup>

在地址面板组件中使用了iconfonts字体图标,在页面中通过使用icon-ringicon-checked来快速展示。

image.png

这里通过在src/styles文件夹中的fonts.scss文件来维护,并在App.vue中引入做全局使用。

image.png

image.png

image.png

登陆模块

由于登录页不属于导航栏页面,无法直接去访问调试,所以在微信开发者工具中去添加编译模式,将需要访问的登录页设置为默认访问的页面。

image.png

给按钮增加点击事件以及open-type开放能力属性,将鼠标移入到属性上可以看到对应支持属性值。(之前安装的vscode插件提供的功能)

image.png

<button class="button phone" open-type="getPhoneNumber" @getphonenumber="onGetPhonenumber">
    <text class="icon icon-phone"></text>
    手机号快捷登录
</button>
const onGetPhonenumber: UniHelper.ButtonOnGetphonenumber = async (e) => {
  const encryptedData = e.detail!.encryptedData
  const iv = e.detail!.iv

  const res = await postLoginWxMinAPI({
    code: wxCode,
    encryptedData,
    iv,
  })
  console.log(res, '---res')
}

由于此处使用的是个人小程序,未进行认证,所以API调用无法成功,下面使用测试接口来模拟登录流程

  <button @tap="onGetPhonenumberSimple">
    <text class="icon icon-phone">模拟快捷登录</text>
  </button>
const onGetPhonenumberSimple: UniHelper.ButtonOnGetphonenumber = async (e) => {
  const res = await postLoginWxMinSimpleAPI('填写手机号码')
  memberStore.setProfile(res.result)

  uni.showToast({
    icon: 'success',
    title: '登录成功',
    success: () => {
      setTimeout(() => {
        uni.switchTab({
          url: '/pages/my/my',
        })
      }, 500)
    },
  })
}

点击模拟快捷登录,成功以后会跳转至「我的」页面(注意这里需要使用switchTab方法来进行导航栏页面的跳转)

image.png

image.png

我的模块

image.png

顶部处使用同样也是使用自定义导航栏

设置页

新建pagesMember目录,通过分包模式在新建「设置」页面。

image.png

image.png

分包subPackages:

访问率高的页面放在主包;

访问率低的页面放在子包按需加载;当用户点击到子包目录中的页面时,还是会有代码包下载的过程,可能会有卡顿的过程,所以子包也不建议拆太大。

可以预先配置可能会跳转到的分包,使用「分包预下载」,在进入页面后根据配置进行预下载。

个人信息页

同样通过分包模式新建该页面

image.png

该页面通过在「设置页」点击头像后跳转进入

image.png

image.png

点击头像需要唤起当前设备的上传文件窗口

image.png

此处需要通过uni-app的「条件编译」能力对API做下兼容处理

条件编译在uni-app中主要用于处理多端差异,通过 #ifdef#ifndef 的方式来实现,后续还会在很多地方使用到。

const onAvatarChange = () => {
  // 调用拍照/选择图片
  // 选择图片条件编译
  // #ifdef H5 || APP-PLUS
  // 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替
  uni.chooseImage({
    count: 1,
    success: (res) => {
      // 文件路径
      const tempFilePaths = res.tempFilePaths
      // 上传
      uploadFile(tempFilePaths[0])
    },
  })
  // #endif

  // #ifdef MP-WEIXIN
  // uni.chooseMedia 仅支持微信小程序端
  uni.chooseMedia({
    // 文件个数
    count: 1,
    // 文件类型
    mediaType: ['image'],
    success: (res) => {
      // 本地路径
      const { tempFilePath } = res.tempFiles[0]
      // 上传
      uploadFile(tempFilePath)
    },
  })
  // #endif
}

地址管理页

使用uni-ui框架的swipe-action组件来实现左滑删除的效果

image.png

在「设置页」点击「我的收货地址」进入 image.png

效果如下

4.gif

SKU模块

SKU:存货单位(Stock Keeping Unit),库存管理的最小可用单元,通常称为"单品"。

SKU模块的实现相对比较固定,在DCloud插件市场也有比较成熟的插件可以使用,这里使用现成插件进行开发。

image.png

image.png image.png

在商品详情页使用SKU插件

<template>
  <vk-data-goods-sku-popup
    v-model="isShowSku"
    :localdata="localdata"
    :mode="mode"
    add-cart-background-color="#FFA868"
    buy-now-background-color="#27BA9B"
    ref="skuPopupRef"
    :actived-style="{
      color: '#27BA9B',
      borderColor: '#27BA9B',
      backgroundColor: '#E9F8F5',
    }"
    @add-cart="onAddCart"
    @buy-now="onBuyNow"
  />
 </template>

效果如下:

5.gif

不知道大家有没有发现一个小细节,为什么这个组件没有在当前页面中引入也能使用,难道是匹配上了easycom模式?

检查下pages.json文件

image.png

可以看到文件中也没有配置如vk-data开头的匹配规则,那是怎么触发easycom的?

原因是开启autoscan自动扫描属性。

image.png

下载复制进项目的插件匹配components/组件名称/组件名称.vue这个规则,所以可以使用easycom模式。

购物车模块

这是购物车模块的页面效果

image.png

当处于未登录状态时,购物车模块展示的内容是不一样的

image.png

在登录时,会往localStorage中存储数据,这里通过pinia获取存储个人信息数据,如果存在就代表登录,反之就是未登录 image.png

import { useMemberStore } from '@/stores'

// 获取会员Store
const memberStore = useMemberStore()
image.png

这里有一处特殊处理,就是购物车主逻辑的内容放在CartMain.vue文件中,购物车页面有两个分别是cart.vuecart2.vue

其中cart.vue作为底部导航栏页面,通过switchTab进行跳转,其左上角不会有返回按钮,但是在商品详情页中,也可以跳转到购物车页面,且左上角需要有返回按钮,所以这里通过两个文件进行维护,两个文件本质上也只是引用CartMain.vue的包装组件而已。

image.png image.png

创建订单页

在购物车页点击结算后,需要跳转到「创建订单页」

image.png

新建pagesOrder目录,并通过分包模式创建create.vue文件

image.png image.png

页面效果如下:

image.png

此处注意创建订单页顶部点击可以进入地址页,

此时如果由于点击事件在最外层容器上,当点击修改时跳转到修改页面,会冒泡到容器的事件上,导致触发对应事件。

所以需要增加阻止事件冒泡以及阻止默认行为。

image.png

商品详情页点击购买时,也需要可以跳转到「创建订单页」

image.png

6.gif

订单详情页面

pagesOrder下通过分包模式新建detail.vue订单详情页,在创建订单页点击提交订单后跳转进来。

image.png

订单详情页增加下骨架屏效果,具体是通过微信开发者工具来生成,再手动处理为.vue文件(步骤同上,如果有点模糊,可以回顾下目录中的「骨架屏」章节~)

image.png

设置过渡动画(微信小程序)

该功能是实现在微信小程序端,其他端有兴趣大家可以自行实现~

小程序框架 / 视图层 / 动画 (qq.com)

image.png
// 基于小程序的 Page 类型扩展 uni-app 的 Page
type PageInstance = Page.PageInstance & WechatMiniprogram.Page.InstanceMethods<any>

// #ifdef MP-WEIXIN
// 获取当前页面实例,数组最后一项
const pageInstance = pages.at(-1) as PageInstance

// 页面渲染完毕,绑定动画效果
onReady(() => {
  // 动画效果,导航栏背景色
  pageInstance.animate(
    '.navbar',
    [{ backgroundColor: 'transparent' }, { backgroundColor: '#f8f8f8' }],
    1000,
    {
      scrollSource: '#scroller',
      timeRange: 1000,
      startScrollOffset: 0,
      endScrollOffset: 50,
    },
  )
  // // 动画效果,导航栏标题
  pageInstance.animate('.navbar .title', [{ color: 'transparent' }, { color: '#000' }], 1000, {
    scrollSource: '#scroller',
    timeRange: 1000,
    startScrollOffset: 0,
    endScrollOffset: 50,
  })
  // // 动画效果,导航栏返回按钮
  pageInstance.animate('.navbar .back', [{ color: '#fff' }, { color: '#000' }], 1000, {
    scrollSource: '#scroller',
    timeRange: 1000,
    startScrollOffset: 0,
    endScrollOffset: 50,
  })
})
// #endif

这里一共设置了三种过渡动画,分别介绍下:

第一个动画,控制.navbar这个class的容器,默认背景色透明,当 id 为 scroller 的容器滚动 50 的距离时,背景色变为#f8f8f8,动画持续 1s。

1.gif

第二个动画 控制导航栏的.title这个class的容器,默认字体颜色透明,当 id 为 scroller 的容器滚动 50 的距离时,字体颜色变为黑色,动画持续 1s。

2.gif

第三个动画 控制导航栏左上角图标所在的容器,默认字体颜色白色,当 id 为 scroller 的容器滚动 50 的距离时,字体颜色变为黑色,动画持续 1s。

3.gif

image.png

image.png

支付倒计时

该效果通过uni-ui中uni-countdown组件来实现。

image.png

服务端也会返回倒计时秒数的时间,通过second属性传入后,会转为分+秒的形式

image.png

点击「去支付」后,注意需要通过redirectTo来跳转,跳转到支付结果后不可返回到当前页。

const onOrderPay = async () => {
  if (import.meta.env.DEV) {
    // 开发环境模拟支付
    await getPayMockAPI({ orderId: query.id })
  } else {
    // #ifdef MP-WEIXIN

    // 正式环境支付:1.获取支付订单信息,2.调用微信支付API
    // const res = await getPayWxPayMiniPayAPI({ orderId: query.id })
    // await wx.requestPayment(res.result)

    await getPayMockAPI({ orderId: query.id })
    // #endif

    // #ifdef H5 || APP-PLUS
    // H5端 和 App 端未开通支付-模拟支付体验
    await getPayMockAPI({ orderId: query.id })
    // #endif
  }
  // 关闭当前页,再跳转支付结果页
  uni.redirectTo({ url: `/pagesOrder/payment/payment?id=${query.id}` })
}

支付结果页

通过分包模式在pagesOrder下新建payment支付结果页

image.png

页面效果:

image.png

点击「查看订单」跳转到订单详情页(同样是不可返回上个页面)

<navigator
  hover-class="none"
  class="button navigator"
  :url="`/pagesOrder/detail/detail?id=${query.id}`"
  open-type="redirect"
>
  查看订单
</navigator>
image.png

支付结果页状态流转的页面展示效果:

image.png image.png image.png image.png image.png

写到最后

以上就是项目中的页面部分的所有内容了。

纯样式类以及普通增删改查的内容就不多加记录了,有兴趣的可以去原视频完整代码中了解。

# 下篇文章内容大纲:
1. 发布上线
2. 打包为安卓及IOS端安装包
3. 跨端兼容
4. uniCloud 云开发

感兴趣的伙伴欢迎点个关注🌟~

感谢你看到这里,如发现文章中的问题或其他感兴趣的内容欢迎指正讨论。^ ^