1.微信小程序开发框架
1.1 框架说明
1.1.1 开发工具、调试工具
- vscode:vscode下载地址
- 微信开发者工具:微信开发者工具(调试基础库:2.31.0)
1.1.2 框架结构
使用vue-cli脚手架创建uni-app框架,vue3 + vite应用框架,uview-plusUI组件库,pinia状态管理器,pnpm依赖包管理。
nodeJs版本要求:^14.18.0 || >=16.0.0
npm:8.5.0 pnpm:6.32.6
vue/cli版本:@5.0.8 vue:^3.2.45 vite:^3.2.4
uview-plus:^3.1.26 pinia:^2.0.30
pnpm install -g @vue/cli vue create -p dcloudio/uni-preset-vue#vite 项目名
1.1.3 框架目录结构
┌─src
│ ├─api 请求地址目录
│ ├─assets 静态资源目录
│ │ └─css 样式
│ │ │ ├─common 公共样式、字体图标样式、uview组件库
│ │ │ └─app.styl 全局样式引入
│ │ └─icon-font 字体图标库
│ └─common
│ │ ├─http 网络请求封装
│ │ ├─bus.js eventBus封装
│ │ └─common.js 公共方法封装
│ ├─component 公共组件
│ ├─conf 系统配置文件
│ ├─pages 主包目录
│ ├─pagesAffairs 办事页面目录
│ ├─pagesCommon 公用页面目录(办理流程、办事指南)
│ ├─pagesIndex 首页页面目录
│ ├─pagesServiced 服务页面目录
│ ├─pagesUser 我的页面目录
│ ├─plugins 插件引入
│ │ └─uview.js uview-plusUI组件库
│ ├─static 静态图片资源目录
│ ├─stores pinia状态管理
│ ├─utils 公用函数、工具目录
│ ├─App.vue 应用配置,用来配置小程序全局样式以及监听、应用生命周期
│ ├─main.js Vue初始化入口文件
│ ├─manifest.json 配置应用名称、appid、logo、版本等打包信息
│ ├─pages.json 配置页面路由、导航条、选项卡等页面类信息
│─.editorconfig 统一代码编辑器配置
│─.eslintignore 统一代码编辑器配置忽略文件
│─.eslintrc.js eslint代码约束配置
│─.gitignore git代码提交忽略文件
│─.prettierrc.json 文件代码格式化配置
│─index.html 静态资源目录
│─jsconfig.json javascript根目录
│─package.json 常见的配置有配置项目启动、打包命令,声明依赖包等
│─pnpm-lock.yaml
│─postcss.config.js css适配方案
└─vite.config.js 项目打包、插件引入、编译相关配置
1.2 组件库
UI组件库: uview-plus@3.1.26
// 安装命令
pnpm install uview-plus@3.1.26
引入方式:
// plugins/uview.js 初始化配置
import uviewPlus from "uview-plus"
export const $toast = (message) => {
uni.$u.toast(message)
}
export default app => {
app.provide('$toast', $toast)
app.use(uviewPlus)
}
// main.js 主入口文件进行引用
import uviewPlusFn from '@/plugins/uview'
// 挂载uviewPlus
uviewPlusFn(app)
// App.vue 引入样式文件
<style lang="scss" scope>
// uview-plus
@import 'uview-plus/index.scss';
// 引入uview-plus主题
@import 'uview-plus/theme.scss';
</style>
// 注意:uview-plus开发还不完善,小程序中相关组件样式不生效,存在报错,需在node_modules中手动引入主题样式
// node_modules/-plus/libs/css/components.css中引入
@import "../../theme.scss";
1.3 状态管理器
项目使用:pinia@2.0.30
// 安装命令
pnpm install pinia@3.1.26
// 使用持久化存储-安装命令
pnpm install pinia-plugin-persist
引入方式:
// stores/index.js 初始化配置
import { createPinia } from "pinia"
import piniaPersist from 'pinia-plugin-persist'
const store = createPinia()
// 持久化存储 小程序暂不支持sessionStorage、localStorage所以不能使用
store.use(piniaPersist)
export function setupStore (app) {
app.use(store)
}
export { store }
// main.js 主入口文件进行引用
import { setupStore } from './stores/index'
// 挂载pinia
setupStore(app))
使用方式:
import { defineStore } from 'pinia'
import { store } from "../index"
export const userCommonStore = defineStore({
id: 'common',
state: () => {
return {
token: ''
}
},
actions: {
// token
setToken (token) {
this.token = token
}
},
persist: {
// 开启持久化存储
enabled: true,
// 自定义持久化参数
strategies: [
{
// 自定义key,默认就是仓库的key
key: 'token',
// 自定义存储方式,默认sessionStorage
storage: localStorage,
// 指定要持久化的数据,默认所有 state 都会进行缓存,可以通过 paths 指定要持久化的字段,其他的则不会进行持久化。
paths: ['token']
}
]
}
})
// 导出状态hook
export function userCommonStoreHook () {
return userCommonStore(store)
}
// vue文件使用
import { userCommonStoreHook } from '@/stores/modules/common'
<script>
// 存储
userCommonStoreHook().setToken('value')
// 获取
const token = userCommonStoreHook().token
</script>
1.4 网络请求封装
项目使用:结合new Promise + uni.request(OBJECT)
相关配置方法:uniapp-request
系统配置:
// conf/index.js 相关系统配置文件
export const API = {
// 开发环境
development: {
DEFAULT: 'http://192.168.1.246:19601/iframework' || import.meta.env.BASE_URL,
JR: 'http://192.168.1.246:19601/iframework' || '/api', // 测试环境
API: 'http://192.168.18.91:8001' // 第三方api
},
// 生产环境
production: {
DEFAULT: 'http://192.168.1.246:19601/iframework' || import.meta.env.BASE_URL,
JR: 'http://192.168.1.246:19601/iframework' || '/api',
API: 'http://192.168.18.91:8001' // 第三方api
}
}
const isProd = import.meta.env.MODE === 'production'
export const BASE_CONFIG = isProd ? API.production : API.development
使用方法:
// common/http/http-async.js 目录文件
'use strict'
import { API as configApi } from '@/conf/'
import qs from 'querystring'
import { getStorage, removeStorage } from '@/utils/storage'
import { linkJump } from '@/common/common'
import { userCommonStoreHook } from '@/stores/modules/common'
// 获取接口根路径
const isProd = import.meta.env.MODE === 'production'
const BASE_PATH = isProd ? configApi.production : configApi.development
export const request = (options) => {
return new Promise((resolve, reject) => {
let url = options.url
// 简化类型设置
const headers = {
'Content-Type': 'application/json; charset=UTF-8',
'Cookie': `JSESSIONID=${getStorage('JSESSIONID') || ''}`
}
// 提交表单类型
if (options.isForm) {
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
delete options.isForm
}
// 提交上传类型
if (options.isFile) {
headers['Content-Type'] = 'multipart/form-data; charset=UTF-8'
delete options.isFile
}
// 校验post数据格式
const contentTyoe = headers['Content-Type']
if (
typeof options.params === 'object' &&
contentTyoe &&
String(contentTyoe).indexOf('application/x-www-form-urlencoded') > -1
) {
options.params = qs.stringify(options.params)
}
// 添加token
if (userCommonStoreHook().token) {
headers['token'] = userCommonStoreHook().token
}
// 设置请求路径
options.url = BASE_PATH[options.apiType || 'JR'] + url
console.log('url', options, options.params)
uni.request({
url: options.url,
method: options.method,
data: options.params,
withCredentials: true,
header: headers,
success (response) {
const status = +response.statusCode
const data = response.data || {}
if (status === 200 || status === 304) {
// 第三方api接口判断
if (data.statusCode === 200 && options.url.indexOf('api.jsp')) {
if (data.statusCode === 200) {
resolve(data)
} else {
uni.$u.toast(data.msg || '')
reject(data)
}
return
}
if (data.id === 500) {
uni.$u.toast(data.msg || '')
// 退出登录
userCommonStoreHook().clearOut()
} else if (data.id === '01') {
resolve(data)
} else if (data.id === '02' && data.msg === 'token不能为空') {
uni.$u.toast(data.msg || '')
// 退出登录
userCommonStoreHook().clearOut()
} else {
uni.$u.toast(data.msg || '')
reject(data)
}
} else if (status === 404) {
linkJump('/pages/404/index')
reject(data)
} else {
reject(data)
}
},
fail (error) {
if (err.errMsg === 'request:fail timeout') {
uni.$u.toast('请求超时,请重试')
} else if (err.errMsg === 'request:fail ') {
uni.$u.toast('无网络')
} else {
uni.$u.toast('服务器繁忙' || error.errMsg)
}
reject(error)
}
})
})
}
请求调用:
'use strict'
import { request } from '@/common/http/http-async'
export const login = (params = {}) => {
const param = {
apiType: 'JR',
method: 'post',
json: true,
url: '/api/internet/login',
params: params
}
return request(param)
}
// 第三方api调用
export const getChannelIdList = (params = {}) => {
const param = {
apiType: 'API',
method: 'get',
json: true,
url: '/api.jsp',
params: params
}
return request(param)
}
// vue文件使用
import { getChannelIdList } from '@/api/index'
let params = {}
getChannelIdList(params).then((result) => {}).catch((err) => {})
1.5 .eslintrc.js和.prettierrc配置
// .eslintrc.js
module.exports = {
// 默认情况下,ESLint会在所有父级组件中寻找配置文件,一直到根目录。ESLint一旦发现配置文件中有 "root": true,它就会停止在父级目录中寻找。
root: true,
env: {
node: true,
es6: true,
'vue/setup-compiler-macros': true
},
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'prettier',
'@vue/prettier',
'plugin:prettier/recommended'
],
plugins: ['prettier'],
// 对Babel解析器的包装使其与 ESLint 兼容。
parserOptions: {
ecmaVersion: 2020,
parse: 'babel-eslint',
// 代码是 ECMAScript 模块
sourceType: 'module'
},
// 去掉校验
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly'
},
rules: {
'prettier/prettier': 'error',
'arrow-body-style': 'off',
'prefer-arrow-callback': 'off',
// 关闭名称校验
'vue/multi-word-component-names': 'off'
}
}
// .prettierrc.json 代码格式化配置
{
"semi": false,
"eslintIntegration": true,
"singleQuote": true,
"endOfLine": "lf",
"tabWidth": 2,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "avoid"
}
2. 项目避坑
2.1 包js文件超过2MB无法运行
原因:微信官方对小程序的体积大小有硬性要求,上限为2048KB
处理方法:
- 分包
- 优化代码,删除掉不用的代码
- 图片压缩或者上传服务器
// manifest.json 相关配置
"mp-weixin": {
// 开启分包优化
"optimization": {
"subPackages": true
}
}
// pages.json 路径配置
// 分包
"subPackages": [
{
"root": "pagesCommon",
"name": "pagesCommon",
"pages": [
{
"path": "handle-event/index",
"style": {
"navigationBarTitleText": "办事",
"enablePullDownRefresh": true,
"navigationStyle": "custom"
}
}
]
},
{
"root": "pagesIndex",
"name": "pagesIndex",
"pages": [
{
"path": "notice-list/index",
"style": {
"navigationBarTitleText": "通知公告",
"enablePullDownRefresh": true,
"navigationStyle": "custom"
}
},
{
"path": "problem-list/index",
"style": {
"navigationBarTitleText": "常见问题",
"enablePullDownRefresh": true,
"navigationStyle": "custom"
}
},
{
"path": "notice-detail/index",
"style": {
"navigationBarTitleText": "通知公告详情",
"enablePullDownRefresh": true,
"navigationStyle": "custom"
}
}
]
}
}
2.2 图片生产环境无法预览
原因:微信小程序background-image只能用网络url或者base64图片编码
处理方法:
- 使用https协议的cdn线上地址访问静态资源(放到服务器访问)
- 通过base64图片访问
/**
* 本地图片转换base64
* @param {*} folder 文件夹名称
* @param {*} fileName 文件名称
* @param {*} format 文件后缀
*/
export const getLocalImgToBase64 = (folder, fileName, format = 'png') => {
let imgUrl = `/static/${folder}/${fileName}.${format}`
let base64 = uni.getFileSystemManager().readFileSync(imgUrl, 'base64')
return `data:image/png;base64,${base64}`
}
// 使用
:style="{ backgroundImage: `url(${getLocalImgToBase64('文件夹名称', '文件名', 'png')})`}"
// 小程序图片模糊失真处理(作用于image图片)
image-rendering -moz-crisp-edges
image-rendering -o-crisp-edges
image-rendering -webkit-optimize-contrast // 设置图像缩放算法
image-rendering crisp-edges // 缩放后不会变模糊
-ms-interpolation-mode nearest-neighbor
2.3 无法显示彩色图标,兼容微信小程序
下载字体包
- 安装插件
// 安装插件
cnpm install -g iconfont-tools
// 进入到文件夹里面用cmd执行下列命令
iconfont-tools
// main.js
// 引入阿里彩色矢量字体
@import '@/assets/icon-font/iconfont.css'
2.4 字体图标显示空白或者报错
下载字体包
ttf文件转换成base64格式,转换地址:transfonter.org
- 将下载后的iconfont.ttf文件引入
@font-face {
font-family 'iconfont'
src url('../../icon-font/iconfont.woff') format('woff'), url('../../icon-font/iconfont.woff2') format('woff2'), url('base64编码') format('truetype')
.iconfont {
font-family 'iconfont' !important
font-size 45rpx
font-style normal
// 抗锯齿
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
}
.icon-jiantou-zuo:before {
content: "\e71b";
}
3. 打包
pnpm run build:mp-weixin
登录微信公众平台,设置 > 账号信息 > AppID,获取到ID后打开微信开发者工具,扫码登录需要发布小程序的微信,右侧基本信息,绑定AppId
- 上传代码,代码包不能超过2MB
打开微信公众平台,设置项目成员、体验成员
修改打开页面路径
如需发布上线,点击提交审核即可