Vue3 – Uni-App 小程序项目配置(Vite+TS+Vue3+Pinia)

简介

本文主要讲解使用 Uni-app 开发多端小程序之前,需要对项目进行配置,以及注意事项。

 

创建项目

使用 HBuildX 创建小程序项目

使用HBX创建非常简单,通过以下步骤就可以实现项目创建

目录结构说明:

|- pages                  业务页面文件存放的目录
|---- index              
|--------index.vue   index页面
|- static                   存放应用引用的本地静态资源的目录(注意:静态资源只能存放于此)
|- unpackage           非工程代码,一般存放运行或发行的编译结果
|- index.html           H5端页面
|- main.js                 Vue初始化入口文件
|- App.vue               配置App全局样式、监听应用生命周期
|- page.json             配置页面路由、导航栏、tabBar等页面类信息
|- manifest.json       配置appid、应用名称、logo、版本等打包信息
|- uni.scss                uni-app内置的常用样式变量

 

使用命令行创建小程序项目

使用命令行如下:

npx degit dcloudio/uni-preset-vue#vite-ts uni-app-vue3-ts

说明
npx包管理器
degit 往github中下载包文件,类似于 npm install 中的 install
dcloudio/uni-preset-vue 是指包名
#vite-ts 包名下的分支
uni-app-vue3-ts 把包下载到本地目录名

 

导入到任意IDE开发工具中

pnpm install

安装依赖

 

pnpm dev:mp-weixin

运行小程序调试

这时在根目录下会生成一个 dev 文件夹,该文件夹为编译后的文件夹,我们通过微信开发者工具导入文件夹中的内容,并在微信开发者工具中运行,既可完成调试。

 

uni-create-view 插件->用于创建页面
uni-helper 插件 -> 代码提示
uniapp小程序扩展 插件 -> 鼠标悬停查文档

安装插件

 

对于TypeScript 而言,我们还需要对TS类型进行配置,通过安装以下包安装ts类型

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

安装完成后,在 tsconfig.json 中加入以下代码启用类型检查

{
  "compilerOptions": {
      "type":[
          "@dcloudio/types",
          "@types/wechat-miniprogram",
          "@uni-helper/uni-app-types"
      ]
    },
    "vueCompilerOptions" : {
         "experimentalRuntimeMode": "runtime-uni-app"
     }
}

 

 

page.json 文件配置说明

pages

pages 属性用于定义需要显示在小程序的页面文件路径,默认排在第一个下标元素会被认为是小程序的首页。

"pages": [ 
    {  页面配置  }
],

 页面配置:

 

path -> 定义页面路径地址

style -> 定义该页面的基本属性,如标题颜色、标题文字等

"style": {
	"navigationBarTitleText": "首页",
	"navigationBarShadow": {
		导航栏阴影 
	},
	"navigationBarBackgroundColor": "导航栏背景颜色",
	"navigationStyle": 导航栏样式,可选 Custom和Default,当Custom时,则关闭导航栏
	"navigationBarTextStyle": 导航栏标题颜色,仅支持 black/white ,
	"enablePullDownRefresh": 是否开启下拉刷新 ,
	"onReachBottomDistance": 页面上拉触底事件触发时距页面底部距离,
	"transparentTitle": 导航栏透明设置。默认 none ,
	"usingComponents": {
		使用自定义组件
	},
	"topWindow": 当存在 topWindow时,当前页面是否显示 topWindow ,
	"leftWindow": 当存在 leftWindow时,当前页面是否显示 leftWindow,
	"rightWindow": 当存在 rightWindow时,当前页面是否显示 rightWindow,
	titlePenetrate": 导航栏点击穿透,
	"backgroundColor": "页面背景颜色 ",
	"backgroundColorBottom": 底部窗口的背景色,
	"backgroundColorTop": 顶部窗口的背景色,
	"backgroundTextStyle": 下拉 loading 的样式 ,
	"mp-weixin": {
		微信小程序特有配置
	},
				
}

 

globalStyle

globalStyle 是用于指定整个小程序的全局配置项,所配置的选项会影响整个小程序

 

tabbar

tabbar属性是用于定义小程序的下方选择栏的,必须至少存在两个选项值才可会显示

"tabBar": {
	"redDotColor": "tabbar上红点颜色 ",
	"selectedColor": "tabbar选中的颜色",
	"color": "tab 上的文字默认颜色",
	"iconWidth": "图标默认宽度(高度等比例缩放)",
	"iconfontSrc": ".ttf文件目录 ",
	"backgroundColor": "tab 的背景色",
	"backgroundImage": "设置图片背景色优先级高于 backgroundColor",
	"backgroundRepeat": "设置标题栏的背景图平铺方式 ",
	"spacing": "图标和文字的间距",
	"list": [
		{
		"pagePath": "页面路径",
		"iconPath": "图标图片路径",
		"iconfont": {
			字体图标,优先级高于 iconPath 
		},
		"selectedIconPath": "选中时的图片路径",
		
		}
	],
}

 

统一代码规范

安装ESLint与Prettier

pnpm i -D eslint prettier eslint-plugin-vue @vue/eslint-config-prettier @vue/eslint-config-typescript @rushstack/eslint-patch @vue/tsconfig

 

新建 .eslintrc.cjs 文件,添加以下 eslint 配置

 

/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')

module.exports = {
  root: true,
  extends: [
    'plugin:vue/vue3-essential',
    'eslint:recommended',
    '@vue/eslint-config-typescript',
    '@vue/eslint-config-prettier',
  ],
  // 小程序全局变量
  globals: {
    uni: true,
    wx: true,
    WechatMiniprogram: true,
    getCurrentPages: true,
    UniApp: true,
    UniHelper: true,
  },
  parserOptions: {
    ecmaVersion: 'latest',
  },
  rules: {
    'prettier/prettier': [
      'warn',
      {
        singleQuote: true,
        semi: false,
        printWidth: 100,
        trailingComma: 'all',
        endOfLine: 'auto',
      },
    ],
    'vue/multi-word-component-names': ['off'],
    'vue/no-setup-props-destructure': ['off'],
    'vue/no-deprecated-html-element-is': ['off'],
    '@typescript-eslint/no-unused-vars': ['off'],
  },
}

 

 

配置 package.json

{
  "script": {
    // ... 省略 ...
    "lint": "eslint . --ext .vue,.js,.ts --fix --ignore-path .gitignore"
  }
}

 

 

运行

pnpm lint

 

 

Git 工作流规范

安装并初始化 husky

pnpm dlx husky-init

安装 lint-staged

pnpm i lint-staged -D

配置 package.json

{
  "script": {
    // ... 省略 ...
  },
  "lint-staged": {
    "*.{vue,ts,js}": ["eslint --fix"]
  }
}

 

 

 

修改 .husky/pre-commit 文件

npm test   // [!code --]
pnpm lint-staged     // [!code ++]

完成 husky + lint-staged 的配置。

 

 

导入uni-ui组件

如果你希望使用 uni-ui 组件,可以根据下面的步骤导入 uni-ui 组件到项目中。

导入组件

pnpm i @dcloudio/uni-ui

配置自动导入组件

 

easycom 是 小程序 中独有的功能,可以实现组件自动导入

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

 

 

安装类型声明文件

因为 uni-ui 使用 js 开发,并没有 ts 的类型声名文件,我们可以通过安装第三方的类型声名组件包

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

配置类型声明文件

 

// tsconfig.json
{
  "compilerOptions": {
    "types": [
      "@dcloudio/types",
      "@uni-helper/uni-app-types", 
      "@uni-helper/uni-ui-types" 
    ]
  }
}

 

 

Pinia 状态管理持久化

安装Pinia状态管理插件

pnpm i pinia

 

 

安装Pinia持久化插件

pnpm i pinia-plugin-persistedstate

在小程序中,并没有 localStrage 的概念,但有

 

// 兼容多端API
uni.setStorageSync()
uni.getStorageSync()

的用法,我们可以使用插件进行自动化持久保存

 

配置 pinia-plugin-persistedstate 插件

因为小程序端没有 localStrage ,因此在配置自动存储时,需要对自动操作做一些调整

其它配置方式详情可参阅:https://megasu.gitee.io/uni-app-shop-note/rabbit-shop/

  {
    persist: {
      storage: {
        getItem(key) {
          return uni.getStorageSync(key)
        },
        setItem(key, value) {
          uni.setStorageSync(key, value)
        },
      },
    },
  },

 

 

设置请求相关拦截器

API拦截器

UniApp 带有拦截器功能,我们可以使用 uni.addInterceptor 对uniapp内的api增加拦截器,实现api方法加工

uni.addInterceptor("被监听的api名",拦截后调用的配置对象)

其中,invoke 方法则是表示在api执行之前,会触发拦截器

/**
 * 小程序中设置请求拦截器,用于在小程序发生请求时对请求数据做一些加工
 * 1. 增加基础URL
 * 2. 修改超时时间
 * 3. 增加小程序请求标识
 * 4. 增加 token
 */
import { useMemberStore } from '@/stores'

const baseURL = ''

const httpInterceptor = {
  // 执行前会被调用
  invoke(options: UniApp.RequestOptions) {
    // 1.增加基础URL
    if (!options.url.startsWith('http')) {
      // 如果请求头是以http开头的,说明不需要加基础链接,如果不是,则说明需要增加基础链接
      options.url = baseURL + options.url
    }

    // 2.修改超时时间
    options.timeout = 10000

    // 3.增加小程序请求标识
    options.header = {
      ...options.header,
      'source-client': 'miniapp',
    }

    // 4.增加 token
    const memberStore = useMemberStore()
    const token = memberStore.profile?.token
    if (token) {
      options.header.Authorization = token
    }
  },
}
// 对两个API增加拦截器,也就是当执行 uni.request 和 uni.uploadFile 时会被先调用拦截器方法
uni.addInterceptor('request', httpInterceptor)
uni.addInterceptor('uploadFile', httpInterceptor)

详情配置可阅读:https://uniapp.dcloud.net.cn/api/interceptor.html#addinterceptor

 

 

封装泛型及Promise返回

我们平时在开发网页时,经常会使用到Axios,它的返回通常都是Promise

在小程序开发中,我们也可以把 uni.request 的api封装成类似 Promise 的返回值,并且我们还可以精确的声明请求后返回的数据类型

我们知道,通常我们的返回值格式都是以下这样的:

{
  code:200,
  msg:"成功"
  data: ...
}

可以看到,除了 data 以外,code和msg都是固定的类型,那么我们就可以声明一个带有泛型的接口:

 

interface ResponseResult<T> {
  code: string
  msg: string
  data: T
}

从上面声明的接口来看,这个接口还会接收一个泛型T,这个T类型将会在以后的请求调用时动态指定,这个类型T将是指定data的类型

 

 

接下来我们可以对 uni.request 进行 Promise 封装

Promise 封装

export const http = <T>(options: UniApp.RequestOptions) => {
  return new Promise<ResponseResult<T>>((resolve, reject) => {
    uni.request({
      ...options,
      // success 的回调,不管是 200 还是 400 还是 500 都会被调用
      success(res) {
        // 当请求为正常响应时
        if (res.statusCode >= 200 && res.statusCode < 300) {
          resolve(res.data as ResponseResult<T>)
        } else if (res.statusCode == 401) {
          // 说明服务器的 token 已过期,要求清理用户信息,并跳到登陆页
          const memberStore = useMemberStore()
          memberStore.clearProfile()
          uni.navigateTo({
            url: '/pages/login/login',
          })
          reject(res)
        } else {
          // 其它的服务器错误
          uni.showToast({
            icon: 'error',
            title: '服务器暂时开了小差,请稍后再试',
          })
          reject((res.data as ResponseResult<T>).msg)
        }
      },
      // 对于完全请求不了的,才会走到错误
      fail(error) {
        uni.showToast({
          icon: 'error',
          title: '网络错误,请检查网络后再试',
        })
        reject(error)
      },
    })
  })
}

 

上面的代码可以看出,调用这个http() 方法的时候,我们必须指定一个类型,即 http<T>(),而这个类型T将决定返回值中的 data 类型。

 

同时我们也可以使用 async - await 的方式来调用 http() 方法

然后我们再利用Promise封装请求成功,或失败后的返回处理。

注意:因为 res 是 uni.request 中返回的数据,本身带有自带类型,会出现类型错误,我们可以直接断言我们声明的类型

 

网络请求规范

页面加载时

当一个页面在打开时,通常我们都会预先加载一屏页面内容,小程序中提供了特有的生命周期 onLoad() 方法,当小程序动行加载后,会调用该生命周期方法,可以在其中加入一些网络加载请求工作。

示例:

// 页面加载时解发
onLoad(async () => {
    getHomeBannerData()
    getHomeCategoryData()
    getHotData()
})

 

一般网络请求规范

前面章节我们讲解了小程序如何封装uni.request请求api,来达到封装Promise的目的,当我们需要向服务器请求数据时,我们应该要另外创建一个文件,用于保存请求url和暴露请求api接口,而不是直接在vue页面中调用网络请求api

示例:

import type { BannerItem, CategoryItem, HotItem } from '@/types/home'
import type { Guess } from '@/types/guess'
import type { PageParams } from '@/types/global'

// 定义一个用于存放请求url的枚举,方便管理
enum HomeApi {
  // 首页广告区域接口
  getHomeBannerURL = '/home/banner',

  ...
}

// 导入 uni.request 封装文件
import { http } from '@/utils/http'

// 暴露封装的请求api
// 首页广告区域请求
export const getHomeBannerApi = (distributionSite = 1) =>
  http<BannerItem[]>({
    method: 'GET',
    url: HomeApi.getHomeBannerURL,
    data: {
      distributionSite,
    },
  })

...

在vue页面调用时,我们则可以通过引入该api接口文件,调用请求即可:

let bannerList = ref<BannerItem[]>([])

// 获取服务器Banner数据
const getHomeBannerData = async () => {
  let res = await getHomeBannerApi()
  bannerList.value = res.result
}

 

网络请求优化

在网络请求中,我们有大量的多个网络请求同时执行的情况,通常我们都会使用 async-await 来获取请求数据,如下示例:

await getHomeBanner()
await getHomeCategory()
await getHomeHot()
await getHomeLike()
...

但是如果使用 await 执行多个请求时,await 的执行是上一行代码执行完后,再去执行下一行代码,这样就会出现单线程请求的情况,效率低下,当多个请求互相不产生数据冲突的情况下,我们没有必要单线程的请求,可以使用 Promise.all 同时执行多个请求,提高效率,如下示例:

await Promise.all([
  getHomeBanner(),
  getHomeCategory(),
  getHomeHot(),
  getHomeLike()
  ...
])

这样的话,所有的请求都会在同时执行,最后一个请求完成后,才会释放,这样能有效的让多个请求同时执行,同时又不会产生跳级执行(即不使用await,不等待直接执行后面的代码)

 

TypeScript 数据类型规范*

关于数据类型,在ts中可做可不做,做了数据类型可以使项目更加规范。

组件数据类型*

每一个自定义组件,其实都有其类型,但如果我们直接调用,IDE是无法知道我们的组件类型的,我们可以通过声明组件类型:

import XtxSwiper from './XtxSwiper.vue'
import XtxGuess from './XtxGuess.vue'

// 通过声明自定义组件类型,当我们在使用组件时,IDE会清楚我们的组件是什么类型
declare module '@vue/runtime-core' {
  export interface GlobalComponent {
    XtxSwiper: typeof XtxSwiper
    XtxGuess: typeof XtxGuess
  }
}

 

ref子组件引用数据类型*

当我们在父组件通过 ref 标记子组件后,其ref也具有类型,其类型声明如何:

export type 子组件ref类型 = InstanceType<typeof 子组件>

提供 InstanceType 封装类型

 

事件处理数据类型*

对于组件的自定义事件处理,如 @click 、@change 等等的事件处理,也具有数据类型,通常如果是 uniapp 自带的组件的事件处理,都可以通过 UniHelper 获取,如下案例是Swiper轮播图组件的 @change 事件的数据类型:

<swiper @change="onChange">
</swiper>
// UniHelper 包含了基本的 uni 组件的数据类型
const onChange:UniHelper.SwiperOnChange = (ev) => {
  ...
}

 

 

 

自定义导航栏

我们平时所看到的小程序导航栏界面,都是小程序默认提供的,但我们可以通过配置,就可以关闭小程序给我们的默认导航栏

  "pages": [
    //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首页",
        "navigationStyle": "custom"
      }
    },
]

 

设置安全区

所谓的安全区,就是手机的状态栏到我们的小程序导航栏的距离,因为不同的手机,它的状态栏都不一样,有高有低,这使得小程序在不同的手机上所展示的导航栏位置会有所不同,容易出现重叠的情况,如下图:

为了解决这种还同手机的高度对导栏所处的位置问题,我们可以通过 uni.getSystemInfoSync() 获取不同手机的状态栏高度,即安全高度

const {safeAreaInsets} = uni.getSystemInfoSync()
console.log(safeAreaInsets)
==>
以 px 为单位
{
bottom: 20
left: 0
right: 0
top: 20
}

通过获取安全区域,则可以动态调整自定义导航栏的高度(或padding)

来实现精准布局

 

页面布局

一个小程序的主要页面布局分别为【导航栏】、【页面内容区】、【菜单栏】三个位置,如下图:

固定导航栏,实现页面内容滚动效果

因为导航栏是我们自定义的,因此,导航栏与页面内容是并列排列的,当我们内容变多时,向下滚动时,会使导航栏一并滚上去

uniapp提供了滚动组件,<scroll-view>,我们可以使用 scroll-view 包裹页面内容,而导航栏我们不使用 scroll-view 包裹,这样就可以使导航栏不出现被滚动上去的情况

scroll-view需要指定高度,否则scroll-view会按照内容大小被无限撑开,只有被设置了固定高度之后,scroll-view才会有滚动效果

设署Flex实现弹性布局

为了解决不同设备屏幕大小的匹配问题,我们不可以设置scroll-view的固定高度,因此我们可以利用Flex布局,把页面中的【导航栏】、【页面内容区】、【菜单栏】三组元素进行弹性布局,这样使得不管什么样的屏幕大小,都可以自动使scroll-view占满屏幕位置

// 设置index主页的整个页面布局为flex
page {
  background-color: #f7f7f7;
  display: flex;
  flex-direction: column;  // 使用列的方式排列布局,默认为横
  height: 100%;  // 使页面占有整个屏幕高度
}
// scroll-view 样式
.scroll {
  flex: 1; // 使 scroll-view 占有 flex 中的大小比例
}

这样可使scroll-view动态占有不同屏中的固定位置和固定大小

 

下拉刷新

scroll-view 提供上拉刷新的功能,我们可以在 scroll-view 上加入属性 refresher-enabled 即可开启上拉刷新功能,而且 scroll-view 还能提供上拉刷新时的图标等功能。

    refresher-enabled  => 开启下拉刷新功能
    @refresherrefresh="onRefresherRefresh" => 下拉刷新功能触发事件
    :refresher-triggered="isTriggered" => 是否处理刷新加载状态
    :refresher-threshold="45" =>  下拉幅度多大触发刷新
    refresher-default-style="black" => 设置下拉刷新时的默认样式
    refresher-background => 下拉刷新时的背影图片

 

上拉触底

当我们上拉内容页时,到页尾时,希望触发新的数据请求,scroll-view 提供了触底事件处理:

<scroll-view
    scroll-y
    // 触底事件处理
    @scrolltolower="onScrollToLower"
    // 触顶事件处理
    @scrolltoupper=""
>
</scroll-view>

 

 

骨架屏

所谓的骨架屏,则是刚小程序刚打开时,网络数据请求未完成时,为了给用户一种加载提示,而预先对内容的大致框架展示的一个组件:

 

骨架屏生成

小程序提供了骨架屏的生成功能,我们可以能过以下操作生成骨架屏:

生成后的骨架屏,会在小程序开发工具上多出wxml和wxss文件:

我们可以利用这两个文件,重新封装成vue组件,使用 v-if 来判断网络请求是否完成,如果未完成,则显示该骨架屏,否则显示真正的数据页面。

<template>
 <!-- 导航栏 -->
  <CustomNavBar />
  <!-- 中间内容页面滚动框 -->
  <scroll-view
    class="scroll"
    scroll-y
    @scrolltolower="onScrollToLower"
    refresher-enabled
    @refresherrefresh="onRefresherRefresh"
    :refresher-triggered="isTriggered"
  >
    <!-- 骨架屏 -->
    <PageSkeleton v-if="isLoading"></PageSkeleton>

     <!-- 中间内容页面滚动框 -->
    <template v-else>
       真正的内容区
    </template>
  </scroll-view>
</template>

 

小程序分包设置

当一个小程序的体积变得比较大时,我们可以对小程序中可能不会常用到的功能分到另一个包中

详情配置规则可查看:https://uniapp.dcloud.net.cn/collocation/pages.html

分包规则

通常我们可以把分包的页面存放在任意地方,但是尽量不要把分包页面存放在pages文件夹中,这样容易与非分包页面产生混乱,我们可以另外新建一个文件夹,用于保存分包页面文件

比如一个小程序,【设置】功能页面可能比较少人,那么我们就可以把【设置】的页面作为分包处理。

  // pages.json 配置分包页面,这些页面不会在小程序启动时马上下载
  "subPackages":[
    // 子包一
    {
      "root":"memberPages",
      "pages": [
        {
          "path": "settings/settings",
          "style": {
            "navigationBarTitleText": "设置",
          }
        }
      ],
    },
    // 子包二
    // {...}
  ],

 

分包预下载

我们可以不设置分包预下载处理,但当我们的分包内容比较大时,在打开时才会下载分包,这样会使用户体验感不太好,因此我们可以设置,当打开什么页面时,系统自动预下载分包内容,这样就可以快速打开分包内容了

设置规则可在pages.json 中定义 preloadRule 属性

  // 配置分包页面预下载规则
  "preloadRule":{
    // 进入 my 页面后,则自动启动预下载分包操作
    "pages/my/my":{
      // 下载分包名称
      "packages":["memberPages"],
      // 下载分包时的网络条件,all与wifi
      "network":"all"
    }
  }

 

 

 

如果您喜欢本站,点击这儿不花一分钱捐赠本站

这些信息可能会帮助到你: 下载帮助 | 报毒说明 | 进站必看

修改版本安卓软件,加群提示为修改者自留,非本站信息,注意鉴别

THE END
分享
二维码
打赏
海报
Vue3 – Uni-App 小程序项目配置(Vite+TS+Vue3+Pinia)
简介 本文主要讲解使用 Uni-app 开发多端小程序之前,需要对项目进行配置,以及注意事项。   创建项目 使用 HBuildX 创建小程序项目 使用HBX创建非常简单,通过以下步骤就可以实现项目创……
<<上一篇
下一篇>>