uniapp
前序准备
首先需要在本地安装node,推荐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开发
插件推荐安装:ESLint、Prettier ESLint、Tailwind CSS IntelliSense、Vue Language Features (Volar)、TypeScript Vue Plugin (Volar)
运行uniapp
打开项目终端:使用vscode打开uniapp目录,在vscode左上角菜单中点击
终端
>新建终端
复制env文件
- 复制
.env.development.example
,将复制的文件名修改为.env.development
- 复制
.env.production.example
,将复制的文件名修改为.env.production
- 复制
打开
.env.development
文件,修改VITE_APP_BASE_URL
变量的值为项目安装部署的服务端地址安装依赖(仅需要安装一次)
在终端中运行命令npm install
运行到h5
在终端中运行命令npm run dev:h5
最终效果如下:
运行到微信小程序
在终端中运行命令npm run dev:mp-weixin
运行完毕,打开微信开发者工具,点击左上角菜单
项目
>导入项目
,导入地址选择uniapp/dist/dev/mp-weixin
,点击确定成功导入项目注意
运行到微信小程序前,先配置好小程序的appid,打开
uniapp/src/manifest.json
,修改"mp-weixin"
>"appid"
字段
发行uniapp
发行到h5
- 在终端中运行命令
npm run build:h5
- 上传打包好的代码到服务器或仓库
注意
发行h5时运行的脚步位于
uniapp/src/scripts/build.h5.mjs
,可以自定义发布路径://uniapp/src/scripts/build.h5.mjs ... //打包发布路径,相对于uniapp根目录路径,谨慎改动,避免覆盖掉重要文件 const releaseRelativePath = '../h5' ...
发行到微信小程序
在终端中运行命令npm run dev:mp-weixin
运行完毕,打开微信开发者工具,点击左上角菜单
项目
>导入项目
,导入地址选择uniapp/dist/build/mp-weixin
,点击确定成功导入项目
点击微信开发者工具的上传
按钮,将代码上传到微信小程序后台
HbuilderX 开发
注意
apple M系列芯片在uniapp下编译的报错处理:在node_modules
下复制粘贴esbuild-darwin-arm64
一份,重命名为esbuild-darwin-64
插件推荐安装:在运行过程中会自动安装需要插件
运行uniapp
- 导入项目,点击HbuilderX左上角菜单
文件
>导入
>从本地目录导入
,目录选择uniapp
- 复制env文件
- 复制
.env.development.example
,将复制的文件名修改为.env.development
- 复制
.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中,你登录的账号不是这个小程序的开发者,只需要去微信小程序后台将该账号添加到开发者,重新运行即可
- 运行到微信小程序前,先配置好小程序的appid,点击
发行uniapp
发行到h5
- 点击HbuilderX左上角菜单
发行
>网站-PC Web或手机H5(仅适用于uni-app)
,输入网站标题,点击发行按钮,编译完成后可在uniapp/dist/build/h5
下面看到打包好的代码 - 将h5下面的代码复制到发布目录,然后上传代码到服务器或仓库即可
- 点击HbuilderX左上角菜单
发行到微信小程序
- 点击HbuilderX左上角菜单
发行
>小程序-微信(仅适用于uni-app)
,输入小程序名称
和小程序appid
,点击发行,编译完成后会自动打开微信开发者工具 - 点击微信开发者工具的
上传
按钮,将代码上传到微信小程序后台
- 点击HbuilderX左上角菜单
环境变量
变量命名规则:需要以VITE_
为前缀的
如何使用:i
更多细节见https://vitejs.cn/guide/env-and-mode.html#env-variables
- .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 页面路由,此外系统也对其进行了扩展,如下:
...
{
"path": "pages/user_set/user_set",
"style": {
"navigationBarTitleText": "个人设置"
},
// 扩展项
"auth": true
// 用于页面跳转拦截,auth为true则代表页面需要登陆才能查看
// 为false则不需要登陆
},
...
路由拦截
路由拦截的原理是通过uni.addInterceptor
对uni.navigateTo
、uni.redirectTo
、uni.reLaunch
、uni.switchTab
等进行拦截,因此必须使用api形式的跳转才能被拦截到uni.addInterceptor
文档详见uniapp拦截器
路由跳转api文档详见uniapp页面和路由
注意
拦截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文档
使用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
样式
项目中使用了scss作为预处理语言,同时也使用了tailwindcss
样式文件位于src/styles
下面:
├──📂 styles
│ ├── dark.css # 深色模式下的css变量
│ ├── public.scss # 全局公共样式
│ ├── index.scss # 入口
│ ├── tailwind.css # 引入tailwindcss样式表
tailwindcss
具体使用说明详见https://tailwindcss.com/
在vscode中安装插件Tailwind CSS IntelliSense,可以得到提示,如果没有提示出现,就按空格键
HbuildX安装插件Tailwind CSS语言服务,点击链接前往插件
点击使用 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.setStorageSync
、uni.getStorageSync
、uni.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 subpackages