uniapp

前序准备

首先需要在本地安装nodeopen in new window,推荐node的版本14.18+、16+。

目录结构

├──📂 .vscode                          # vscode配置文件
├──📂 scripts                          # js脚本
├──📂 src                              # 源代码
│  ├──📂 api                           # 所有请求
│  ├──📂 components                    # 全局公用组件
│  ├──📂 enums                         # 全局枚举
│  ├──📂 hooks                         # 全局hook
│  ├──📂 pages                         # 所有页面
│  ├──📂 plugins                       # 插件安装
│  ├──📂 router                        # 路由拦截
│  ├──📂 static                        # 静态资源文件
│  ├──📂 stores                        # 全局状态管理
│  ├──📂 styles                        # 全局样式
│  ├──📂 uni_modules                   # uniapp插件市场引入的包
│  ├──📂 utils                         # 全局公用方法
│  ├── App.vue                         # 入口页面
│  ├── main.ts                         # 入口文件 初始化,组册插件等
│  └── manifest.json                   # uniapp各端相关配置
│  └── pages.json                      # 页面配置
│  └── uni.scss                        # 全局scss变量
├──📂 typings                          # ts声明文件
├── index.html                         # H5html
├── .env.xxx                           # 环境变量配置
├── .eslintrc.cjs                      # eslint 配置项
├── package.json                       # package.json
├── tailwind.config.js                 # tailwindcss 配置项
├── tsconfig.json                      # ts 配置项
├── vite.config.ts                     # vite 配置项

Visual Studio Code开发

插件推荐安装:ESLintopen in new windowPrettier ESLintopen in new windowTailwind CSS IntelliSenseopen in new windowVue Language Features (Volar)open in new windowTypeScript Vue Plugin (Volar)open in new window

运行uniapp

  • 打开项目终端:使用vscode打开uniapp目录,在vscode左上角菜单中点击终端>新建终端

  • 复制env文件

    1. 复制.env.development.example,将复制的文件名修改为.env.development
    2. 复制.env.production.example,将复制的文件名修改为.env.production
  • 打开.env.development文件,修改VITE_APP_BASE_URL变量的值为项目安装部署的服务端地址

  • 安装依赖(仅需要安装一次)
    在终端中运行命令

    npm install
    
  • 运行到h5
    在终端中运行命令

    npm run dev:h5
    

    最终效果如下:

  • 运行到微信小程序
    在终端中运行命令

    npm run dev:mp-weixin
    

    运行完毕,打开微信开发者工具open in new window,点击左上角菜单项目>导入项目,导入地址选择uniapp/dist/dev/mp-weixin,点击确定成功导入项目

    注意

    运行到微信小程序前,先配置好小程序的appid,打开uniapp/src/manifest.json,修改"mp-weixin">"appid"字段

发行uniapp

  • 发行到h5

    1. 在终端中运行命令
    npm run build:h5
    
    1. 上传打包好的代码到服务器或仓库

    注意

    发行h5时运行的脚步位于uniapp/src/scripts/build.h5.mjs,可以自定义发布路径:

        //uniapp/src/scripts/build.h5.mjs
        ...
        //打包发布路径,相对于uniapp根目录路径,谨慎改动,避免覆盖掉重要文件
        const releaseRelativePath = '../h5'
        ...
    
  • 发行到微信小程序
    在终端中运行命令

    npm run dev:mp-weixin
    

    运行完毕,打开微信开发者工具open in new window,点击左上角菜单项目>导入项目,导入地址选择uniapp/dist/build/mp-weixin,点击确定成功导入项目
    点击微信开发者工具的上传按钮,将代码上传到微信小程序后台

HbuilderX 开发

注意

apple M系列芯片在uniapp下编译的报错处理:在node_modules下复制粘贴esbuild-darwin-arm64一份,重命名为esbuild-darwin-64

插件推荐安装:在运行过程中会自动安装需要插件

运行uniapp

  • 导入项目,点击HbuilderX左上角菜单文件>导入>从本地目录导入,目录选择uniapp
  • 复制env文件
    1. 复制.env.development.example,将复制的文件名修改为.env.development
    2. 复制.env.production.example,将复制的文件名修改为.env.production
  • 打开.env.development文件,修改VITE_APP_BASE_URL变量的值为项目安装部署的服务端地址
  • 安装依赖,选中当前项目,点击HbuilderX左上角菜单工具>外部命令>npm install安装依赖
  • 运行到h5,点击HbuilderX左上角菜单运行>运行到浏览器>Chrome
  • 运行到微信小程序,点击HbuilderX左上角菜单运行>运行到小程序模拟器>微信开发者工具 - (uniapp)

    注意

    • 运行到微信小程序前,先配置好小程序的appid,点击uniapp/src/manifest.json,选择微信小程序配置>微信小程序AppID,输入appid即可
    • 一般运行到微信小程序,会自动打开微信开发者工具,如果打开失败,可能是工具的服务端口没有打开,手动打开工具 -> 设置 -> 安全设置,将服务端口开启,也有可能是你配置的小程序appid中,你登录的账号不是这个小程序的开发者,只需要去微信小程序后台将该账号添加到开发者,重新运行即可

发行uniapp

  • 发行到h5

    1. 点击HbuilderX左上角菜单发行>网站-PC Web或手机H5(仅适用于uni-app),输入网站标题,点击发行按钮,编译完成后可在uniapp/dist/build/h5下面看到打包好的代码
    2. 将h5下面的代码复制到发布目录,然后上传代码到服务器或仓库即可
  • 发行到微信小程序

    1. 点击HbuilderX左上角菜单发行>小程序-微信(仅适用于uni-app),输入小程序名称小程序appid,点击发行,编译完成后会自动打开微信开发者工具
    2. 点击微信开发者工具的上传按钮,将代码上传到微信小程序后台

环境变量

变量命名规则:需要以VITE_为前缀的
如何使用:import.meta.env.VITE_
更多细节见https://vitejs.cn/guide/env-and-mode.html#env-variablesopen in new window

  • .env.development
    开发环境适用
NODE_ENV = 'development'

# 请求域名
VITE_APP_BASE_URL='https://likeadmin.yixiangonline.com'  
  • .env.production
    生产环境适用
NODE_ENV = 'production'

# 请求域名
VITE_APP_BASE_URL=''  //填空则跟着网站的域名来请求

路由

页面配置

页面配置文件位于uniapp/src/pages.json,如何配置请参考uniapp pages.json 页面路由open in new window,此外系统也对其进行了扩展,如下:

    ...
    {
        "path": "pages/user_set/user_set",
        "style": {
            "navigationBarTitleText": "个人设置"
        },
        // 扩展项
        "auth": true 
        // 用于页面跳转拦截,auth为true则代表页面需要登陆才能查看
        // 为false则不需要登陆
    },
    ...

路由拦截

路由拦截的原理是通过uni.addInterceptoruni.navigateTouni.redirectTouni.reLaunchuni.switchTab等进行拦截,因此必须使用api形式的跳转才能被拦截到
uni.addInterceptor文档详见uniapp拦截器open in new window
路由跳转api文档详见uniapp页面和路由open in new window

注意

拦截uni.switchTab本身没有问题。但是在微信小程序端点击tabbar的底层逻辑并不是触发uni.switchTab。所以误认为拦截无效,此类场景的解决方案是在tabbar页面的页面生命周期onShow中处理。

接口请求

系统中使用uni.request来发起请求,并对其进行了更深一步的封装,位于src/utils/request

├──📂 request
│  ├── http.ts    # 封装的HttpRequest实例
│  ├── cancel.ts   # 封装的取消重复请求实例
│  ├── index.ts    # 接口返回统一处理及默认配置
│  ├── type.d.ts   # 类型声明文件

一般只需要修改index.ts文件,其他文件无需修改
index.ts文件说明:

默认配置

const defaultOptions: HttpRequestOptions = {
    // uni.request的参数
    requestOptions: {
        timeout: 10 * 1000
    },
    // 请求的baseurl
    baseUrl: `${import.meta.env.VITE_APP_BASE_URL || ''}/`,
    //是否返回默认的响应
    isReturnDefaultResponse: false,
    // 需要对返回数据进行处理
    isTransformResponse: true,
    // 接口拼接地址
    urlPrefix: 'api',
    // 忽略重复请求
    ignoreCancel: false,
    // 是否携带token
    withToken: true,
    // 是否需要登录,如果为true,且后台返回的状态码为-1,则会自动重定向到登录页面
    isAuth: false,
    // 接口请求失败重试次数,目前只针对get请求
    retryCount: 2,
    // 重试延时
    retryTimeout: 1000,
    // 请求拦截钩子函数
    requestHooks: requestHooks
}

请求拦截器

const requestHooks: RequestHooks = {
    requestInterceptorsHook(options, config) {
        const { urlPrefix, baseUrl, withToken, isAuth } = config
        options.header = options.header ?? {}
        if (urlPrefix) {
            options.url = `${urlPrefix}${options.url}`
        }
        if (baseUrl) {
            options.url = `${baseUrl}${options.url}`
        }
        const token = getToken()
        // 添加token
        if (withToken && !options.header.token) {
            options.header.token = token
        }
        return options
    },
    responseInterceptorsHook(response, config) {
        const { isTransformResponse, isReturnDefaultResponse, isAuth } = config

        //返回默认响应,当需要获取响应头及其他数据时可使用
        if (isReturnDefaultResponse) {
            return response
        }
        // 是否需要对数据进行处理
        if (!isTransformResponse) {
            return response.data
        }
        const { logout } = useUserStore()
        const { code, data, msg, show } = response.data as any
        switch (code) {
            case RequestCodeEnum.SUCCESS:
                msg && show && uni.$u.toast(msg)
                return data
            case RequestCodeEnum.FAILED:
                uni.$u.toast(msg)
                return Promise.reject(msg)

            case RequestCodeEnum.TOKEN_INVALID:
                logout()
                if (isAuth && !getToken()) {
                    uni.navigateTo({
                        url: '/pages/login/login'
                    })
                }
                return Promise.reject()

            default:
                return data
        }
    },
    responseInterceptorsCatchHook(options) {
        if (options.method?.toUpperCase() == RequestMethodsEnum.POST) {
            uni.$u.toast('请求失败,请重试')
        }
        return Promise.reject()
    }
}

如何在单个接口中单独使用这些配置

// 配置
export function xxxx(data) {
    return request.post({ 
        url: 'xxx',
        header: {
            'Content-type': ContentTypeEnum.FORM_DATA
        },
        data
    }, {
        isAuth: true,
        // 忽略重复请求
        ignoreCancelToken: true,
        // 开启请求超时重新发起请求请求机制
        isOpenRetry: false,
         // 需要对返回数据进行处理
        isTransformResponse: false,
    })
}

组件注册

位于uniapp/src/components中的组件无需注册,不过需要遵循uniapp easycom规则,需符合components/组件名称/组件名称.vue目录结构,也可以自定义规则,详见文档uniapp easycom文档open in new window

使用vue插件

下面以pinia为例子: 在src/plugins/modules下面新建一个文件pinia.ts

// pinia.ts 
import { App } from 'vue'
import { createPinia } from 'pinia'
const pinia = createPinia()
export default (app: App) => {
    app.use(pinia)
}


这样就完成了插件的注册,不需要将文件引入到main.ts

样式

项目中使用了scssopen in new window作为预处理语言,同时也使用了tailwindcssopen in new window
样式文件位于src/styles下面:

├──📂 styles
│  ├── dark.css       # 深色模式下的css变量
│  ├── public.scss    # 全局公共样式
│  ├── index.scss     # 入口
│  ├── tailwind.css   # 引入tailwindcss样式表

tailwindcss

具体使用说明详见https://tailwindcss.com/open in new window
在vscode中安装插件Tailwind CSS IntelliSenseopen in new window,可以得到提示,如果没有提示出现,就按空格键

HbuildX安装插件Tailwind CSS语言服务open in new window,点击链接前往插件

点击使用 HBuilderX 导入插件安装插件

tailwindcss配置说明

/** @type {import('tailwindcss').Config} */
module.exports = {
    content: ['./index.html', './src/**/*.{html,js,ts,jsx,tsx,vue}'],
    theme: {
        colors: {
            //白色
            white: '#ffffff',
            //黑色
            black: '#000000',
            // 主要字体
            main: '#333333',
            // 次要字体
            content: '#666666',
            // 浅色字体
            muted: '#999999',
            // 更浅色字体
            light: '#e5e5e5',
            // 主题色
            primary: {
                DEFAULT: '#4173ff'
            },
            // 成功
            success: '#5ac725',
            // 警告
            warning: '#f9ae3d',
            // 错误
            error: '#f56c6c',
            // 信息
            info: '#909399',
            // 页面背景
            page: '#f6f6f6'
        },
        fontSize: {
            xs: '24rpx',
            sm: '26rpx',
            base: '28rpx',
            lg: '30rpx',
            xl: '32rpx',
            '2xl': '34rpx',
            '3xl': '38rpx',
            '4xl': '40rpx',
            '5xl': '44rpx'
        }
    },
    plugins: [],
    corePlugins: {
        preflight: false
    }
}

页面,组件的样式

  • 样式穿透

开启scoped属性后需要如果需要将样式作用到子组件上,可以这样处理:

<style scoped>
:deep(.el-menu-item) {
    
}
</style>

使用本地存储

项目中对本地存储进行了封装,基于uni.setStorageSyncuni.getStorageSyncuni.removeStorageSync实现,位于src/utils/cache.ts,推荐使用时搭配cacheEnums.ts一起使用

设置缓存:

// src/enums/cacheEnums.ts
export const TOKEN_KEY = 'token'  

// xxx/xxx.ts
import { TOKEN_KEY } from '@/enums/cacheEnums'
cache.set(TOKEN_KEY, 'xxxx') 
//带有时间的缓存
cache.set(TOKEN_KEY, 'xxxx', 10 * 12 *  3600) // 10 * 12 *  3600为缓存时间,单位为s

获取缓存:

import { TOKEN_KEY } from '@/enums/cacheEnums'
cache.get(TOKEN_KEY)

删除缓存:

import { TOKEN_KEY } from '@/enums/cacheEnums'
cache.remove(TOKEN_KEY)

微信公众号JSSDK

项目中对微信公众号JSSDK常用的接口进行了封装,位于uniapp/src/utils/wechat.ts,代码及介绍如下

const wechatOa = {
    //获取微信配置url,由于安卓与苹果在SPA下
    //苹果的url为第一次进入的页面,而安卓的则会随着页面路由而切换
    getSignLink() {
        if (typeof window.signLink === 'undefined' || window.signLink === '') {
            window.signLink = location.href.split('#')[0]
        }
        return isAndroid() ? location.href.split('#')[0] : window.signLink
    },
    // 获取公众号登录url
    getUrl() {
        getWxCodeUrl().then((res) => {
            location.href = res.url
        })
    },
    // 公众号登录
    authLogin(code: string) {
        return new Promise((resolve, reject) => {
            OALogin({
                code
            })
                .then((res) => {
                    resolve(res)
                })
                .catch((err) => {
                    reject(err)
                })
        })
    },
    ready() {
        return new Promise((resolve) => {
            weixin.ready(() => {
                resolve('success')
            })
        })
    },
    // 公众号分享
    share(options: Record<any, any>) {
        this.ready().then(() => {
            const { shareTitle, shareLink, shareImage, shareDesc } = options
            weixin.updateTimelineShareData({
                title: shareTitle, // 分享标题
                link: shareLink, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                imgUrl: shareImage // 分享图标
            })
            // 发送给好友
            weixin.updateAppMessageShareData({
                title: shareTitle, // 分享标题
                link: shareLink, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                imgUrl: shareImage, // 分享图标
                desc: shareDesc
            })
            // 发送到tx微博
            weixin.onMenuShareWeibo({
                title: shareTitle, // 分享标题
                link: shareLink, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                imgUrl: shareImage, // 分享图标
                desc: shareDesc
            })
        })
    },
    // 获取地址
    getAddress() {
        return new Promise((reslove, reject) => {
            this.ready().then(() => {
                weixin.openAddress({
                    success: (res: any) => {
                        reslove(res)
                    },
                    fail: (res: any) => {
                        reject(res)
                    }
                })
            })
        })
    },
    // 获取定位
    getLocation() {
        return new Promise((reslove, reject) => {
            this.ready().then(() => {
                weixin.getLocation({
                    type: 'gcj02',
                    success: (res: any) => {
                        reslove(res)
                    },
                    fail: (res: any) => {
                        reject(res)
                    }
                })
            })
        })
    }
}

分包建议

由于小程序有体积和资源加载限制,因此尽量确保每个包的大小小于2M,微信小程序支持总体积一共不能超过20M,因此多分几个包也无所谓,还有主包包含了公共代码,因此尽量将新的页面写到分包里去。 如何分包及更多细节请见文档uniapp subpackagesopen in new window

上次更新:
贡献者: Jason, lr