从零开始开发Uni-app+微信小程序全教程
本教程将带领开发者从零搭建Uni-app项目,适配微信小程序端,集成小程序核心功能(如用户登录、本地存储、页面路由、接口请求等),最终完成项目打包、预览与发布。全程配套详细实操步骤与完整代码示例,适合前端新手或需要快速落地微信小程序项目的开发者,无需单独学习小程序原生语法,依托Uni-app跨端能力高效开发。
一、前置知识与环境准备
1.1 必备知识
-
基础HTML/CSS/JavaScript语法
-
Vue.js基础(Uni-app基于Vue语法,推荐Vue3+TypeScript)
-
微信小程序基本概念(AppID、小程序后台、分包加载等)
1.2 环境搭建
1.2.1 安装Uni-app开发工具(HBuilderX)
Uni-app推荐使用HBuilderX开发,内置Uni-app编译环境与小程序打包工具,操作简单:
-
下载HBuilderX:https://www.dcloud.io/hbuilderx.html,选择「正式版」,根据系统(Windows/Mac)选择对应版本
-
安装完成后打开HBuilderX,进入「工具」-「插件安装」,搜索「Uni-app」插件(默认已安装),确保插件启用
1.2.2 安装微信开发者工具
用于预览、调试Uni-app编译后的微信小程序,以及最终提交审核:
-
下载微信开发者工具:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html,选择对应系统版本
-
安装完成后打开,登录微信账号(需与小程序开发者账号绑定),开启「服务端口」:进入「设置」-「安全设置」,勾选「开启服务端口」(用于HBuilderX联动调试)
1.2.3 微信小程序账号与AppID准备
开发小程序需提前注册账号并获取AppID(测试与发布必需):
-
注册/登录微信小程序后台:https://mp.weixin.qq.com/(选择「小程序」类型账号)
-
获取AppID:登录后进入「开发」-「开发设置」,记录「AppID(小程序ID)」(后续项目配置需用到)
-
添加开发者:进入「成员管理」-「开发者/体验者」,添加当前开发微信账号,获得开发权限
二、创建Uni-app+微信小程序项目
2.1 初始化Uni-app项目
-
打开HBuilderX,点击「文件」-「新建」-「项目」
-
选择「Uni-app」模板,输入项目名称(如「uniapp-wechat-miniprogram」),框架选择「Vue3」,勾选「TypeScript」(可选,推荐),模板选择「默认模板」,点击「创建」
-
项目核心目录说明:
-
pages:存放页面文件(每个页面对应一个文件夹,包含vue、json、js等文件)
-
static:存放静态资源(图片、字体等,注意:小程序端静态资源大小有限制,建议分包或使用CDN)
-
main.ts:项目入口文件(初始化Vue实例、引入全局依赖)
-
pages.json:Uni-app核心配置文件(配置页面路由、窗口样式、tabBar等)
-
manifest.json:项目配置文件(配置小程序AppID、权限等)
-
uni_modules:存放Uni-app插件(可通过插件市场下载复用)
-
2.2 配置小程序基础信息(manifest.json)
打开项目根目录的「manifest.json」,配置小程序相关信息(确保能正常编译与预览):
-
点击「微信小程序配置」选项卡,勾选「已申请AppID」,填入第一步获取的「小程序AppID」
-
配置「小程序项目名称」「小程序官方主页」(可选,发布时需一致)
-
权限配置:根据项目需求添加权限(如获取用户信息、地理位置等),示例:勾选「获取用户信息」「获取当前位置」
2.3 本地运行小程序预览
将Uni-app项目编译为微信小程序代码,并在微信开发者工具中预览:
-
右键项目根目录,选择「运行」-「运行到小程序模拟器」-「微信开发者工具」
-
HBuilderX会自动编译项目,生成小程序代码(默认路径:unpackage/dist/dev/mp-weixin),并自动打开微信开发者工具加载项目
-
若未自动打开,手动打开微信开发者工具,选择「导入项目」,导入上述编译后的mp-weixin文件夹,点击「预览」,即可看到Uni-app默认首页(若出现白屏,检查AppID是否配置正确、开发者账号是否有权限)
三、Uni-app适配小程序核心配置
3.1 页面路由配置(pages.json)
pages.json用于配置小程序的页面路由、窗口样式、tabBar等,示例配置(包含首页、列表页、详情页、个人中心):
{
"pages": [
{
"path": "pages/index/index", // 首页路径
"style": {
"navigationBarTitleText": "首页", // 导航栏标题
"navigationBarBackgroundColor": "#FFFFFF", // 导航栏背景色
"navigationBarTextStyle": "black" // 导航栏文字颜色(black/white)
}
},
{
"path": "pages/list/list",
"style": {
"navigationBarTitleText": "列表页"
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "详情页"
}
},
{
"path": "pages/mine/mine",
"style": {
"navigationBarTitleText": "个人中心"
}
}
],
"globalStyle": {
"navigationBarBackgroundColor": "#FFFFFF",
"navigationBarTextStyle": "black",
"backgroundColor": "#F5F5F5" // 页面背景色
},
"tabBar": { // 底部tabBar配置(可选,用于多tab页面切换)
"color": "#666666", // 未选中文字颜色
"selectedColor": "#1E88E5", // 选中文字颜色
"backgroundColor": "#FFFFFF", // 背景色
"borderStyle": "black", // 边框颜色(black/white)
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/tabbar/home.png", // 未选中图标
"selectedIconPath": "static/tabbar/home-active.png" // 选中图标
},
{
"pagePath": "pages/list/list",
"text": "列表",
"iconPath": "static/tabbar/list.png",
"selectedIconPath": "static/tabbar/list-active.png"
},
{
"pagePath": "pages/mine/mine",
"text": "我的",
"iconPath": "static/tabbar/mine.png",
"selectedIconPath": "static/tabbar/mine-active.png"
}
]
}
}
3.2 小程序特有配置(page.json)
若单个页面需要特殊配置(如禁用下拉刷新、自定义导航栏),可在对应页面文件夹下创建「page.json」文件,示例(详情页禁用下拉刷新):
{
"enablePullDownRefresh": false, // 禁用下拉刷新
"disableScroll": false, // 允许页面滚动
"navigationStyle": "default" // 默认导航栏(可设为custom自定义)
}
3.3 封装全局请求工具(utils/request.ts)
基于uni.request封装全局请求工具,处理请求拦截、响应拦截、错误提示,适配小程序端网络请求限制:
import { showToast } from 'uni-app';
// 基础接口地址(开发环境/生产环境可区分配置)
const BASE_URL = process.env.NODE_ENV === 'development' ? 'https://test-api.example.com' : 'https://api.example.com';
// 封装请求函数
const request = <T = any>(options: {
url: string;
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
data?: any;
header?: any;
}) => {
return new Promise<T>((resolve, reject) => {
uni.request({
url: BASE_URL + options.url,
method: options.method || 'GET',
data: options.data || {},
header: {
'Content-Type': 'application/json',
// 可添加全局token(如从本地存储获取)
'Authorization': uni.getStorageSync('token') || '',
...options.header
},
success: (res) => {
const statusCode = res.statusCode;
// 响应状态码判断
if (statusCode === 200) {
const data = res.data as T;
resolve(data);
} else {
showToast({
title: res.data?.message || `请求失败(${statusCode})`,
icon: 'none',
duration: 2000
});
reject(res.data);
}
},
fail: (err) => {
showToast({
title: '网络错误,请稍后重试',
icon: 'none',
duration: 2000
});
reject(err);
}
});
});
};
// 封装GET请求
export const get = <T = any>(url: string, params?: any, header?: any) => {
return request<T>({ url, method: 'GET', params, header });
};
// 封装POST请求
export const post = <T = any>(url: string, data?: any, header?: any) => {
return request<T>({ url, method: 'POST', data, header });
};
export default request;
3.4 封装微信登录工具(utils/wechat.ts)
封装小程序用户登录逻辑(获取code、换取openid、存储用户信息),需后端配合实现code验证接口:
import { showToast, showLoading } from 'uni-app';
import { post } from './request';
// 微信登录响应类型
interface LoginResponse {
code: number;
message: string;
data: {
token: string;
openid: string;
userInfo: {
nickname: string;
avatarUrl: string;
};
};
}
/**
* 微信小程序登录(获取code并换取用户信息)
*/
export const wechatLogin = async () => {
try {
showLoading({ title: '登录中...' });
// 1. 获取微信登录code(小程序特有接口)
const { code } = await uni.login();
if (!code) {
throw new Error('获取登录code失败');
}
// 2. 调用后端接口,用code换取openid、token、用户信息
const res = await post<LoginResponse>('/api/wechat/login', { code });
if (res.code === 200) {
const { token, openid, userInfo } = res.data;
// 3. 存储用户信息到本地(小程序本地存储)
uni.setStorageSync('token', token);
uni.setStorageSync('openid', openid);
uni.setStorageSync('userInfo', userInfo);
showToast({ title: '登录成功' });
return userInfo;
} else {
throw new Error(res.message || '登录失败');
}
} catch (error) {
const errMsg = (error as Error).message || '登录异常';
showToast({ title: errMsg, icon: 'none' });
throw error;
} finally {
uni.hideLoading();
}
};
/**
* 退出登录(清除本地存储)
*/
export const wechatLogout = () => {
uni.removeStorageSync('token');
uni.removeStorageSync('openid');
uni.removeStorageSync('userInfo');
showToast({ title: '退出登录成功' });
};
/**
* 获取本地存储的用户信息
*/
export const getUserInfo = () => {
return uni.getStorageSync('userInfo') || null;
};
3.5 小程序分包加载配置
微信小程序主包体积限制为2M,当项目较大(含多页面、静态资源)时,需通过分包加载拆分代码,降低主包体积。Uni-app支持自动适配小程序分包语法,核心通过pages.json配置subPackages实现。
3.5.1 分包目录结构设计
推荐将非首页、非tabBar页面拆分至分包,示例目录结构:
pages/ // 主包(存放首页、tabBar页面)
├─ index/index.vue // 首页(主包必需)
├─ list/list.vue // 列表页(tabBar页面,主包)
├─ mine/mine.vue // 个人中心(tabBar页面,主包)
└─ detail/detail.vue // 详情页(可放主包或分包)
subPackages/ // 分包根目录(自定义名称,需在pages.json配置)
├─ packageA/ // 分包A(例如:关于我们、帮助中心等辅助页面)
│ └─ about/about.vue
│ └─ help/help.vue
└─ packageB/ // 分包B(例如:商品相关、订单相关页面)
└─ goods/goods.vue
└─ order/order.vue
3.5.2 pages.json分包配置
在pages.json中新增subPackages字段,配置分包路径、根目录及页面路由,示例:
{
"pages": [
{
"path": "pages/index/index",
"style": { "navigationBarTitleText": "首页" }
},
{
"path": "pages/list/list",
"style": { "navigationBarTitleText": "列表页" }
},
{
"path": "pages/mine/mine",
"style": { "navigationBarTitleText": "个人中心" }
},
{
"path": "pages/detail/detail",
"style": { "navigationBarTitleText": "详情页" }
}
],
"subPackages": [
{
"root": "subPackages/packageA", // 分包A根目录
"pages": [
{
"path": "about/about", // 页面路径(相对分包根目录,完整路径:subPackages/packageA/about/about)
"style": { "navigationBarTitleText": "关于我们" }
},
{
"path": "help/help",
"style": { "navigationBarTitleText": "帮助中心" }
}
]
},
{
"root": "subPackages/packageB", // 分包B根目录
"pages": [
{
"path": "goods/goods",
"style": { "navigationBarTitleText": "商品详情" }
},
{
"path": "order/order",
"style": { "navigationBarTitleText": "我的订单" }
}
]
}
],
"globalStyle": { /* 原有配置不变 */ },
"tabBar": { /* 原有配置不变 */ }
}
3.5.3 分包跳转与注意事项
-
跳转分包页面:与主包页面跳转语法一致,直接使用完整路径或相对路径,示例:
// 方式1:使用完整路径 uni.navigateTo({ url: '/subPackages/packageA/about/about' }); // 方式2:使用相对路径(从当前页面出发)uni.navigateTo({ url: '../../subPackages/packageA/about/about' }); -
注意事项:tabBar页面必须放在主包,不能放在分包中
-
分包之间、分包与主包之间可自由跳转,但需使用正确的跳转API(navigateTo/redirectTo等,不支持switchTab跳分包页面)
-
分包体积限制:单个分包体积≤2M,所有分包总和≤20M
-
静态资源:分包内的静态资源建议放在对应分包目录下,避免占用主包体积
四、核心功能开发实战
4.1 首页开发(pages/index/index.vue)
实现功能:页面加载时自动登录、展示用户信息、配置下拉刷新、添加页面跳转按钮:
<template>
<view class="index-container">
<!-- 用户信息展示 -->
<view class="user-info" v-if="userInfo">
<image :src="userInfo.avatarUrl" class="avatar"></image>
<view class="nickname">{{ userInfo.nickname }}</view>
</view>
<view class="btn-group">
<button @click="toListPage" class="btn">进入列表页</button>
<button @click="logout" class="btn logout-btn">退出登录</button>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'uni-app';
import { wechatLogin, wechatLogout, getUserInfo } from '@/utils/wechat';
const router = useRouter();
const userInfo = ref<any>(null);
// 页面加载时初始化
onMounted(() => {
initPage();
// 配置下拉刷新
uni.startPullDownRefresh();
});
// 初始化页面(获取用户信息,未登录则自动登录)
const initPage = async () => {
const localUserInfo = getUserInfo();
if (localUserInfo) {
userInfo.value = localUserInfo;
} else {
// 未登录,自动触发微信登录
const info = await wechatLogin();
userInfo.value = info;
}
// 停止下拉刷新
uni.stopPullDownRefresh();
};
// 跳转列表页
const toListPage = () => {
router.push('/pages/list/list');
};
// 退出登录
const logout = () => {
wechatLogout();
userInfo.value = null;
};
</script>
<style scoped>
.index-container {
padding: 20rpx;
box-sizing: border-box;
min-height: 100vh;
background-color: #F5F5F5;
}
.user-info {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 40rpx;
padding: 30rpx;
background-color: #FFFFFF;
border-radius: 20rpx;
}
.avatar {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
margin-bottom: 20rpx;
}
.nickname {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.btn-group {
display: flex;
flex-direction: column;
gap: 30rpx;
}
.btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
font-size: 32rpx;
background-color: #1E88E5;
color: #FFFFFF;
border-radius: 40rpx;
}
.logout-btn {
background-color: #FF4D4F;
}
</style>
4.2 列表页开发(pages/list/list.vue)
实现功能:请求接口获取列表数据、下拉刷新、上拉加载更多、点击跳转详情页:
<template>
<view class="list-container">
<!-- 列表项 -->
<view class="list-item" v-for="(item, index) in listData" :key="index" @click="toDetailPage(item.id)">
<view class="item-title">{{ item.title }}</view>
<view class="item-desc">{{ item.desc }}</view>
<view class="item-time">{{ item.createTime }}</view>
</view>
<!-- 加载提示 -->
<view class="load-more" v-if="loading">加载中...</view>
<view class="no-more" v-if="noMore">没有更多数据了</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'uni-app';
import { get } from '@/utils/request';
const router = useRouter();
// 列表数据
const listData = ref<Array<{
id: number;
title: string;
desc: string;
createTime: string;
}>>([]);
// 分页参数
const page = ref(1);
const pageSize = ref(10);
// 加载状态
const loading = ref(false);
const noMore = ref(false);
// 页面加载时获取列表数据
onMounted(() => {
getListData();
// 监听下拉刷新
uni.onPullDownRefresh(() => {
refreshList();
});
// 监听上拉加载更多
uni.onReachBottom(() => {
loadMoreData();
});
});
// 获取列表数据
const getListData = async () => {
try {
loading.value = true;
const res = await get<{
code: number;
data: {
list: typeof listData.value;
total: number;
};
}>('/api/list', { page: page.value, pageSize: pageSize.value });
if (res.code === 200) {
const { list, total } = res.data;
if (page.value === 1) {
// 第一页,覆盖数据
listData.value = list;
} else {
// 非第一页,追加数据
listData.value = [...listData.value, ...list];
}
// 判断是否还有更多数据
noMore.value = listData.value.length >= total;
}
} catch (error) {
console.error('获取列表数据失败:', error);
} finally {
loading.value = false;
uni.stopPullDownRefresh();
}
};
// 下拉刷新
const refreshList = () => {
page.value = 1;
noMore.value = false;
getListData();
};
// 上拉加载更多
const loadMoreData = () => {
if (!loading.value && !noMore.value) {
page.value++;
getListData();
}
};
// 跳转详情页(携带参数)
const toDetailPage = (id: number) => {
router.push({
path: '/pages/detail/detail',
query: { id } // 携带列表项ID
});
};
</script>
<style scoped>
.list-container {
padding: 20rpx;
box-sizing: border-box;
background-color: #F5F5F5;
min-height: 100vh;
}
.list-item {
padding: 20rpx;
margin-bottom: 20rpx;
background-color: #FFFFFF;
border-radius: 20rpx;
}
.item-title {
font-size: 32rpx;
font-weight: bold;
color: #333333;
margin-bottom: 10rpx;
}
.item-desc {
font-size: 28rpx;
color: #666666;
margin-bottom: 10rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.item-time {
font-size: 24rpx;
color: #999999;
}
.load-more, .no-more {
text-align: center;
padding: 20rpx;
font-size: 28rpx;
color: #999999;
}
</style>
4.3 详情页开发(pages/detail/detail.vue)
实现功能:接收列表页参数、请求详情数据、展示数据、返回上一页:
<template>
<view class="detail-container" v-if="detailData">
<view class="detail-title">{{ detailData.title }}</view>
<view class="detail-time">{{ detailData.createTime }}</view>
<view class="detail-content">{{ detailData.content }}</view>
<button @click="goBack" class="back-btn">返回上一页</button>
</view>
<view class="loading" v-else>加载中...</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRoute } from 'uni-app';
import { get } from '@/utils/request';
const route = useRoute();
// 详情数据
const detailData = ref<{
id: number;
title: string;
createTime: string;
content: string;
}>(null);
// 页面加载时获取详情数据
onMounted(() => {
const id = route.query.id as string;
if (id) {
getDetailData(Number(id));
} else {
uni.showToast({ title: '参数错误', icon: 'none' });
uni.navigateBack();
}
});
// 获取详情数据
const getDetailData = async (id: number) => {
try {
const res = await get<{
code: number;
data: typeof detailData.value;
}>(`/api/detail/${id}`);
if (res.code === 200) {
detailData.value = res.data;
}
} catch (error) {
console.error('获取详情数据失败:', error);
uni.showToast({ title: '获取详情失败', icon: 'none' });
}
};
// 返回上一页
const goBack = () => {
uni.navigateBack({ delta: 1 });
};
</script>
<style scoped>
.detail-container {
padding: 20rpx;
box-sizing: border-box;
background-color: #FFFFFF;
min-height: 100vh;
}
.detail-title {
font-size: 36rpx;
font-weight: bold;
color: #333333;
margin-bottom: 20rpx;
text-align: center;
}
.detail-time {
font-size: 24rpx;
color: #999999;
text-align: center;
margin-bottom: 30rpx;
}
.detail-content {
font-size: 28rpx;
color: #666666;
line-height: 48rpx;
}
.back-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
font-size: 32rpx;
background-color: #1E88E5;
color: #FFFFFF;
border-radius: 40rpx;
margin-top: 40rpx;
}
.loading {
text-align: center;
padding: 50rpx;
font-size: 32rpx;
color: #666666;
}
</style>
4.4 个人中心开发(pages/mine/mine.vue)
实现功能:展示本地存储的用户信息、退出登录、跳转关于页面:
<template>
<view class="mine-container">
<!-- 用户信息卡片 -->
<view class="user-card">
<image :src="userInfo?.avatarUrl || '/static/default-avatar.png'" class="avatar"></image>
<view class="user-name">{{ userInfo?.nickname || '未登录' }}</view>
</view>
<!-- 功能列表 -->
<view class="func-list">
<view class="func-item" @click="goToAbout">
<view class="item-text">关于我们</view>
<image src="/static/arrow-right.png" class="arrow-icon"></image>
</view>
<view class="func-item" @click="logout" v-if="userInfo">
<view class="item-text" style="color: #FF4D4F;">退出登录</view>
</view>
<view class="func-item" @click="login" v-else>
<view class="item-text" style="color: #1E88E5;">立即登录</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'uni-app';
import { wechatLogin, wechatLogout, getUserInfo } from '@/utils/wechat';
const router = useRouter();
const userInfo = ref<any>(null);
// 页面加载时获取用户信息
onMounted(() => {
getUserInfoFromLocal();
});
// 从本地存储获取用户信息
const getUserInfoFromLocal = () => {
const info = getUserInfo();
userInfo.value = info;
};
// 登录
const login = async () => {
const info = await wechatLogin();
userInfo.value = info;
};
// 退出登录
const logout = () => {
wechatLogout();
userInfo.value = null;
};
// 跳转关于我们页面
const goToAbout = () => {
router.push('/pages/about/about');
};
</script>
<style scoped>
.mine-container {
padding: 0;
box-sizing: border-box;
background-color: #F5F5F5;
min-height: 100vh;
}
.user-card {
background-color: #1E88E5;
padding: 40rpx 20rpx;
text-align: center;
color: #FFFFFF;
}
.avatar {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
border: 4rpx solid #FFFFFF;
margin-bottom: 20rpx;
}
.user-name {
font-size: 32rpx;
font-weight: bold;
}
.func-list {
margin-top: 20rpx;
background-color: #FFFFFF;
}
.func-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20rpx;
height: 88rpx;
border-bottom: 1rpx solid #F5F5F5;
}
.item-text {
font-size: 32rpx;
color: #333333;
}
.arrow-icon {
width: 24rpx;
height: 40rpx;
}
</style>
五、高级功能集成:小程序支付
小程序支付需微信商户号与小程序账号绑定,核心流程:前端发起支付请求→后端生成预支付订单→前端调用微信支付API→支付结果回调处理。以下是Uni-app适配小程序支付的完整实现。
5.1 前置准备
-
注册微信商户号:https://pay.weixin.qq.com/,完成实名认证
-
绑定小程序:在商户号后台「产品中心」-「AppID授权管理」,绑定当前小程序AppID
-
获取商户密钥(key):在商户号后台「账户中心」-「API安全」,设置API密钥(后续后端接口需使用)
-
后端准备:开发支付相关接口(创建预支付订单、查询支付结果、接收支付回调通知)
5.2 封装支付工具(utils/pay.ts)
封装小程序支付核心逻辑,包括调用后端预支付接口、唤起微信支付面板、处理支付结果:
import { showToast, showLoading, requestPayment } from 'uni-app';
import { post } from './request';
// 预支付订单响应类型(后端返回)
interface PrepayResponse {
code: number;
message: string;
data: {
timeStamp: string; // 时间戳(微信支付要求为字符串)
nonceStr: string; // 随机字符串
package: string; // 预支付订单号(格式:prepay_id=xxx)
signType: 'MD5' | 'HMAC-SHA256'; // 签名类型
paySign: string; // 支付签名
};
}
/**
* 小程序支付核心函数
* @param orderId 订单ID(前端传入,用于后端查询订单信息)
* @returns 支付结果(成功/失败)
*/
export const wechatPay = async (orderId: number) => {
try {
showLoading({ title: '发起支付中...' });
// 1. 调用后端接口,获取预支付订单信息(核心参数由后端生成)
const res = await post<PrepayResponse>('/api/pay/createOrder', { orderId });
if (res.code !== 200) {
throw new Error(res.message || '获取预支付订单失败');
}
const { timeStamp, nonceStr, package: prepayPackage, signType, paySign } = res.data;
// 2. 调用微信支付API(Uni-app封装的requestPayment,自动适配小程序)
const payResult = await requestPayment({
provider: 'weixin', // 支付提供商(小程序固定为weixin)
timeStamp,
nonceStr,
package: prepayPackage,
signType,
paySign
});
// 3. 支付成功处理(实际业务中需再次调用后端接口验证支付结果,避免前端伪造)
showToast({ title: '支付成功' });
// 验证支付结果(可选,增强安全性)
const verifyRes = await post<{ code: number; message: string }>('/api/pay/verify', { orderId });
if (verifyRes.code === 200) {
return true; // 支付成功且验证通过
} else {
throw new Error('支付结果验证失败,请稍后查询订单状态');
}
} catch (error) {
const errMsg = (error as Error).message || '支付失败';
// 区分用户主动取消和其他错误
if (errMsg.includes('cancel') || errMsg.includes('取消')) {
showToast({ title: '已取消支付', icon: 'none' });
} else {
showToast({ title: errMsg, icon: 'none' });
}
throw error;
} finally {
uni.hideLoading();
}
};
5.3 支付页面开发(示例:订单确认页)
在订单确认页添加支付按钮,调用上述支付工具,示例代码(pages/order/confirm.vue):
<template>
<view class="order-confirm-container">
<!-- 订单信息展示 -->
<view class="order-info">
<view class="order-item">订单ID:{{ orderId }}</view>
<view class="order-item">订单金额:¥{{ amount.toFixed(2) }}</view>
</view>
<!-- 支付按钮 -->
<button @click="handlePay" class="pay-btn">立即支付</button>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'uni-app';
import { wechatPay } from '@/utils/pay';
import { get } from '@/utils/request';
const route = useRoute();
const router = useRouter();
const orderId = ref<number>(0);
const amount = ref<number>(0);
// 页面加载时获取订单信息
onMounted(() => {
const id = route.query.orderId as string;
if (id) {
orderId.value = Number(id);
getOrderInfo();
} else {
uni.showToast({ title: '订单参数错误', icon: 'none' });
uni.navigateBack();
}
});
// 获取订单信息
const getOrderInfo = async () => {
try {
const res = await get<{
code: number;
data: { orderId: number; amount: number };
}>(`/api/order/info/${orderId.value}`);
if (res.code === 200) {
amount.value = res.data.amount;
}
} catch (error) {
console.error('获取订单信息失败:', error);
}
};
// 处理支付
const handlePay = async () => {
try {
const paySuccess = await wechatPay(orderId.value);
if (paySuccess) {
// 支付成功后跳转至订单详情页
router.push(`/pages/order/detail?orderId=${orderId.value}`);
}
} catch (error) {
console.error('支付失败:', error);
}
};
</script>
<style scoped>
.order-confirm-container {
padding: 20rpx;
box-sizing: border-box;
min-height: 100vh;
background-color: #F5F5F5;
}
.order-info {
background-color: #FFFFFF;
padding: 20rpx;
border-radius: 20rpx;
margin-bottom: 40rpx;
}
.order-item {
font-size: 28rpx;
color: #333333;
margin-bottom: 15rpx;
}
.pay-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
font-size: 32rpx;
background-color: #FF4D4F;
color: #FFFFFF;
border-radius: 40rpx;
}
</style>
5.4 支付结果回调处理
-
前端回调:上述wechatPay函数中,支付成功后会触发resolve,可在此处跳转页面、更新订单状态
-
后端回调:微信支付成功后,会向商户号后台配置的「回调通知地址」发送POST请求,后端需处理该回调(验证签名、更新订单状态、记录支付日志),前端可通过轮询或WebSocket获取最终订单状态(避免前端回调不可靠问题)
5.5 注意事项
-
支付接口域名需配置:在小程序后台「开发」-「开发设置」-「服务器域名」,添加支付相关接口域名(需备案,HTTPS)
-
签名验证:前端无需处理签名生成,所有签名(paySign)由后端基于商户密钥生成,确保安全性
-
测试环境:可使用微信支付沙箱环境(商户号后台申请)进行测试,避免真实支付
-
错误处理:需兼容用户主动取消支付、网络异常、订单过期等多种异常场景
<template>
<view class="detail-container" v-if="detailData">
<!-- 自定义分享按钮(需设置open-type="share") -->
<button open-type="share" class="share-btn">分享给好友</button>
<!-- 页面原有内容 -->
<view class="detail-title">{{ detailData.title }}</view>
<!-- 省略其他内容... -->
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRoute } from 'uni-app';
import { get } from '@/utils/request';
import { getShareConfig } from '@/utils/share';
const route = useRoute();
const detailData = ref<{
id: number;
title: string;
content: string;
coverImage: string;
}>(null);
// 页面加载时获取详情数据
onMounted(() => {
const id = route.query.id as string;
if (id) {
getDetailData(Number(id));
}
});
const getDetailData = async (id: number) => {
// 省略原有逻辑...
};
// 1. 配置右上角菜单分享(好友)
const onShareAppMessage = () => {
if (detailData.value) {
// 动态拼接分享路径(携带详情页id,确保跳转后能正确加载数据)
const sharePath = `/pages/detail/detail?id=${detailData.value.id}`;
return getShareConfig(
detailData.value.title, // 用文章标题作为分享标题
sharePath,
detailData.value.coverImage // 用文章封面作为分享图片
);
}
// 数据未加载完成时的默认配置
return getShareConfig();
};
// 2. 配置右上角菜单分享(朋友圈)- 可选
const onShareTimeline = () => {
if (detailData.value) {
return {
title: detailData.value.title,
query: `id=${detailData.value.id}`, // 分享参数
imageUrl: detailData.value.coverImage
};
}
return {
title: '默认分享标题',
query: '',
imageUrl: '/static/share-default.png'
};
};
// 暴露生命周期函数(Vue3 setup语法必需)
defineExpose({
onShareAppMessage,
onShareTimeline
});
</script>
<style scoped>
/* 新增分享按钮样式 */
.share-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
font-size: 32rpx;
background-color: #1E88E5;
color: #FFFFFF;
border-radius: 40rpx;
margin: 20rpx 0;
}
/* 省略其他样式... */
</style>
六、常用扩展功能集成
6.1 微信地图定位功能
实现获取用户当前地理位置、解析地理位置信息(如省市区),需用到微信小程序getLocation接口和腾讯地图SDK(可选,用于逆地址解析)。
6.1.1 前置准备
-
在小程序后台「开发」-「开发设置」-「接口设置」中,开启「获取当前地理位置」接口
-
若需解析地址,注册腾讯地图开发者账号,申请WebService API密钥(https://lbs.qq.com/)
-
在manifest.json中添加定位权限:微信小程序配置→权限配置→勾选「获取当前位置」
6.1.2 封装定位工具(utils/location.ts)
import { showToast, showLoading, getLocation } from 'uni-app';
import { get } from './request';
// 腾讯地图SDK基础地址(用于逆地址解析)
const TENCENT_MAP_API = 'https://apis.map.qq.com/ws/geocoder/v1/';
// 替换为自己的腾讯地图API密钥
const TENCENT_MAP_KEY = 'YOUR_TENCENT_MAP_KEY';
/**
* 获取用户当前地理位置(经纬度+地址信息)
* @param type 定位类型:wgs84(GPS坐标)、gcj02(国测局坐标,小程序默认)
* @returns 地理位置信息
*/
export const getUserLocation = async (type: 'wgs84' | 'gcj02' = 'gcj02') => {
try {
showLoading({ title: '获取位置中...' });
// 1. 获取经纬度
const { latitude, longitude } = await getLocation({ type });
if (!latitude || !longitude) {
throw new Error('获取经纬度失败');
}
// 2. 调用腾讯地图API解析地址(可选)
const res = await get<{
status: number;
result: {
address: string; // 详细地址
address_component: {
province: string; // 省
city: string; // 市
district: string; // 区
street: string; // 街道
};
};
}>(TENCENT_MAP_API, {
location: `${latitude},${longitude}`,
key: TENCENT_MAP_KEY,
get_poi: 0 // 不获取周边POI
});
if (res.status !== 0) {
throw new Error('地址解析失败');
}
const { address, address_component } = res.result;
return {
latitude,
longitude,
address,
province: address_component.province,
city: address_component.city,
district: address_component.district
};
} catch (error) {
const errMsg = (error as Error).message || '获取位置失败';
showToast({ title: errMsg, icon: 'none' });
throw error;
} finally {
uni.hideLoading();
}
};
6.1.3 页面中使用定位功能
<template>
<view class="location-container">
<button @click="getLocation" class="location-btn">获取当前位置</button>
<view class="location-info" v-if="locationData">
<view>当前地址:{{ locationData.address }}</view>
<view>经纬度:{{ locationData.latitude }}, {{ locationData.longitude }}</view>
<view>省份:{{ locationData.province }}</view>
<view>城市:{{ locationData.city }}</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { getUserLocation } from '@/utils/location';
const locationData = ref<{
latitude: number;
longitude: number;
address: string;
province: string;
city: string;
district: string;
}>(null);
const getLocation = async () => {
try {
const data = await getUserLocation();
locationData.value = data;
} catch (error) {
console.error('获取位置失败:', error);
}
};
</script>
6.2 本地存储进阶(异步存储+数据加密)
基础本地存储(uni.setStorageSync)为同步操作,大量数据存储会阻塞主线程;敏感数据(如用户token、手机号)需加密存储。以下封装异步加密存储工具。
6.2.1 封装加密存储工具(utils/storage.ts)
import { showToast } from 'uni-app';
// 简单加密工具(实际项目建议使用crypto-js等专业加密库)
const encrypt = (data: string, key: string = 'YOUR_STORAGE_KEY') => {
let result = '';
for (let i = 0; i < data.length; i++) {
result += String.fromCharCode(data.charCodeAt(i) + key.charCodeAt(i % key.length));
}
return result;
};
const decrypt = (data: string, key: string = 'YOUR_STORAGE_KEY') => {
let result = '';
for (let i = 0; i < data.length; i++) {
result += String.fromCharCode(data.charCodeAt(i) - key.charCodeAt(i % key.length));
}
return result;
};
/**
* 异步加密存储
* @param key 存储键名
* @param data 存储数据(支持任意类型)
*/
export const setStorage = async (key: string, data: any) => {
try {
const stringData = JSON.stringify(data);
const encryptedData = encrypt(stringData);
await uni.setStorage({ key, data: encryptedData });
return true;
} catch (error) {
showToast({ title: '存储失败', icon: 'none' });
console.error('存储失败:', error);
return false;
}
};
/**
* 异步解密获取存储
* @param key 存储键名
* @returns 解密后的原始数据
*/
export const getStorage = async (key: string) => {
try {
const { data } = await uni.getStorage({ key });
if (!data) return null;
const decryptedData = decrypt(data);
return JSON.parse(decryptedData);
} catch (error) {
showToast({ title: '获取存储失败', icon: 'none' });
console.error('获取存储失败:', error);
return null;
}
};
/**
* 异步删除存储
* @param key 存储键名
*/
export const removeStorage = async (key: string) => {
try {
await uni.removeStorage({ key });
return true;
} catch (error) {
showToast({ title: '删除存储失败', icon: 'none' });
console.error('删除存储失败:', error);
return false;
}
};
6.2.2 工具使用示例
// 存储用户信息
await setStorage('userInfo', { nickname: '张三', avatarUrl: 'xxx' });
// 获取用户信息
const userInfo = await getStorage('userInfo');
// 删除用户信息
await removeStorage('userInfo');
6.3 小程序消息订阅功能
实现用户订阅消息后,后端可通过接口推送模板消息(如订单通知、活动提醒),需先在小程序后台申请消息模板。
6.3.1 前置准备
-
登录微信小程序后台,进入「功能」-「订阅消息」,申请所需消息模板(如“订单支付成功通知”),记录模板ID
-
在manifest.json中添加订阅消息权限:微信小程序配置→权限配置→勾选「订阅消息」
6.3.2 封装订阅工具(utils/subscribe.ts)
import { showToast, requestSubscribeMessage } from 'uni-app';
/**
* 发起消息订阅
* @param tmplIds 消息模板ID数组(最多3个)
* @returns 订阅结果
*/
export const subscribeMessage = async (tmplIds: string[]) => {
try {
if (tmplIds.length === 0) {
throw new Error('请传入消息模板ID');
}
// 调用微信订阅消息接口
const res = await requestSubscribeMessage({ tmplIds });
// 处理订阅结果(res为{模板ID: 'accept'|'reject'|'ban'})
const result: { accept: string[]; reject: string[] } = { accept: [], reject: [] };
for (const [tmplId, status] of Object.entries(res)) {
if (status === 'accept') {
result.accept.push(tmplId);
} else {
result.reject.push(tmplId);
}
}
if (result.accept.length > 0) {
showToast({ title: `成功订阅${result.accept.length}类消息` });
}
if (result.reject.length > 0) {
showToast({ title: `拒绝订阅${result.reject.length}类消息`, icon: 'none' });
}
return result;
} catch (error) {
const errMsg = (error as Error).message || '订阅消息失败';
showToast({ title: errMsg, icon: 'none' });
throw error;
}
};
6.3.3 页面中发起订阅
<template>
<view class="subscribe-container">
<button @click="handleSubscribe" class="subscribe-btn">订阅订单通知</button>
</view>
</template>
<script setup lang="ts">
import { subscribeMessage } from '@/utils/subscribe';
// 替换为自己申请的消息模板ID
const ORDER_NOTICE_TMPL_ID = 'YOUR_ORDER_NOTICE_TMPL_ID';
const handleSubscribe = async () => {
try {
const result = await subscribeMessage([ORDER_NOTICE_TMPL_ID]);
console.log('订阅结果:', result);
} catch (error) {
console.error('订阅失败:', error);
}
};
</script>
6.3.4 注意事项
-
订阅消息需用户主动触发(如点击按钮),不能自动弹出订阅弹窗
-
同一模板ID,用户拒绝后需引导用户去小程序设置页重新开启订阅(通过uni.openSetting)
-
订阅消息有有效期,单次订阅仅支持推送一次,长期订阅需申请长期模板(仅特定类目支持)
6.4 小程序分享功能
小程序分享分为「页面内按钮分享」和「右上角菜单分享」,Uni-app通过onShareAppMessage和onShareTimeline生命周期函数适配,可实现自定义分享标题、图片、路径等功能。
6.4.1 封装分享工具(utils/share.ts)
封装通用分享配置,统一管理分享内容,支持动态修改参数:
import { showToast } from 'uni-app';
/**
* 生成通用分享配置
* @param title 分享标题
* @param path 分享路径(需携带必要参数,如页面id)
* @param imageUrl 分享图片(建议尺寸2.35:1,不小于300*157)
* @returns 分享配置对象
*/
export const getShareConfig = (
title: string = '默认分享标题',
path: string = '/pages/index/index',
imageUrl: string = '/static/share-default.png'
) => {
return {
title,
path,
imageUrl,
// 分享成功回调
success: () => {
showToast({ title: '分享成功' });
},
// 分享失败回调
fail: (err: any) => {
console.error('分享失败:', err);
showToast({ title: '分享失败', icon: 'none' });
}
};
};
6.4.2 页面内实现分享功能
在需要分享的页面(如详情页)中,通过生命周期函数配置分享,同时可添加自定义分享按钮:
<template>
<view class="detail-container" v-if="detailData">
<!-- 自定义分享按钮(需设置open-type="share") -->
<button open-type="share" class="share-btn">分享给好友</button>
<!-- 页面原有内容 -->
<view class="detail-title">{{ detailData.title }}</view>
<view class="detail-time">{{ detailData.createTime }}</view>
<view class="detail-content">{{ detailData.content }}</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRoute } from 'uni-app';
import { get } from '@/utils/request';
import { getShareConfig } from '@/utils/share';
const route = useRoute();
const detailData = ref<{
id: number;
title: string;
createTime: string;
content: string;
coverImage: string;
}>(null);
// 页面加载时获取详情数据
onMounted(() => {
const id = route.query.id as string;
if (id) {
getDetailData(Number(id));
}
});
const getDetailData = async (id: number) => {
// 省略原有逻辑...
};
// 1. 配置右上角菜单分享(好友)
const onShareAppMessage = () => {
if (detailData.value) {
const sharePath = `/pages/detail/detail?id=${detailData.value.id}`;
return getShareConfig(
detailData.value.title,
sharePath,
detailData.value.coverImage
);
}
return getShareConfig();
};
// 2. 配置右上角菜单分享(朋友圈)- 可选
const onShareTimeline = () => {
if (detailData.value) {
return {
title: detailData.value.title,
query: `id=${detailData.value.id}`,
imageUrl: detailData.value.coverImage
};
}
return {
title: '默认分享标题',
query: '',
imageUrl: '/static/share-default.png'
};
};
// 暴露生命周期函数(Vue3 setup语法必需)
defineExpose({
onShareAppMessage,
onShareTimeline
});
</script>
<style scoped>
.share-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
font-size: 32rpx;
background-color: #1E88E5;
color: #FFFFFF;
border-radius: 40rpx;
margin-bottom: 30rpx;
}
/* 省略其他样式... */
</style>
6.4.3 注意事项
-
分享路径必须是pages.json中已配置的页面,且不能携带超过1024字节的参数
-
分享图片建议使用网络图片或本地静态图片,尺寸推荐500*400px,避免使用页面截图
-
onShareTimeline仅支持微信小程序基础库2.11.3及以上版本,需在小程序后台配置最低基础库版本
-
页面内自定义分享按钮必须设置open-type=“share”,否则无法触发分享逻辑
6.5 小程序客服功能集成
小程序客服功能支持用户与商家实时沟通,核心通过button组件的open-type="contact"实现,可配置自动回复、快捷回复等功能,无需前端复杂开发。
6.5.1 前置准备
-
登录微信小程序后台,进入「功能」-「客服」,开启客服功能
-
配置客服人员:在「客服」-「客服人员管理」中,添加客服微信号(需绑定小程序主体)
-
可选配置:设置自动回复、快捷回复、菜单导航等(在客服后台配置)
6.5.2 页面集成客服入口
在需要添加客服的页面(如个人中心、订单页),使用button组件设置open-type=“contact”,示例代码:
<template>
<view class="mine-container">
<!-- 原有个人中心内容 -->
<view class="func-list">
<view class="func-item" @click="goToAbout">
<view class="item-text">关于我们</view>
<image src="/static/arrow-right.png" class="arrow-icon"></image>
</view>
<!-- 客服入口按钮 -->
<button open-type="contact" class="service-btn">
<image src="/static/service-icon.png" class="service-icon"></image>
<view class="service-text">联系客服</view>
</button>
</view>
</view>
</template>
<script setup lang="ts">
// 省略原有逻辑...
</script>
<style scoped>
.service-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 88rpx;
background-color: #FFFFFF;
border: none;
margin-top: 20rpx;
}
.service-icon {
width: 40rpx;
height: 40rpx;
margin-right: 15rpx;
}
.service-text {
font-size: 32rpx;
color: #333333;
}
/* 省略其他样式... */
</style>
6.5.3 客服消息推送配置(可选)
若需实现客服消息实时推送至客服人员微信,需在小程序后台「客服」-「消息推送」中配置:
-
消息推送模式:选择「推送到微信客服系统」(默认),客服人员可通过微信客服助手小程序接收消息
-
自定义推送:若需推送到自有服务器,可配置「开发者模式」,设置消息接收地址、加密方式等(需后端配合开发)
6.5.4 注意事项
-
客服按钮样式可自定义,但需保留清晰的客服标识,避免用户误解
-
客服功能仅在真机上可正常使用,模拟器中可能无法触发
-
需告知用户客服在线时间,提升用户体验
-
敏感内容过滤:微信会自动过滤客服消息中的敏感词汇,避免违规
6.6 小程序广告植入功能集成
小程序支持接入微信广告平台(流量主)的多种广告形式,如Banner广告、插屏广告、激励视频广告等,可实现流量变现。以下以常用的Banner广告和插屏广告为例,介绍集成流程。
6.6.1 前置准备
-
小程序需满足流量主申请条件:累计独立访客(UV)≥1000,且无违规记录
-
登录微信小程序后台,进入「流量主」,申请开通流量主功能,审核通过后创建广告位,获取广告位ID
-
广告位类型选择:根据页面场景选择(Banner广告适合嵌入页面底部/顶部,插屏广告适合页面切换时展示)
6.6.2 Banner广告集成
Banner广告是固定在页面顶部或底部的横幅广告,集成步骤如下:
<template>
<view class="list-container">
<!-- 列表内容 -->
<view class="list-item" v-for="(item, index) in listData" :key="index">
<view class="item-title">{{ item.title }}</view>
<view class="item-desc">{{ item.desc }}</view>
</view>
<!-- Banner广告容器 -->
<view class="banner-ad-container" v-if="adShow"></view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { get } from '@/utils/request';
const listData = ref<Array<any>>([]);
const adShow = ref(false);
let bannerAd: WechatMiniprogram.BannerAd | null = null;
// 页面加载时创建Banner广告
onMounted(() => {
getListData();
createBannerAd();
});
// 页面卸载时销毁广告,避免内存泄漏
onUnmounted(() => {
if (bannerAd) {
bannerAd.destroy();
bannerAd = null;
}
});
// 获取列表数据(原有逻辑)
const getListData = async () => {
// 省略原有逻辑...
};
// 创建Banner广告
const createBannerAd = () => {
// 替换为自己的Banner广告位ID
const adUnitId = 'adunit-xxxxxxxxxxxxxxxx';
// 创建广告实例
bannerAd = uni.createBannerAd({
adUnitId,
adIntervals: 30, // 广告自动刷新间隔(30-120秒)
style: {
width: uni.getSystemInfoSync().windowWidth, // 广告宽度(建议与屏幕宽度一致)
height: 100 // 广告高度(根据广告位要求设置)
}
});
// 监听广告加载成功
bannerAd.onLoad(() => {
console.log('Banner广告加载成功');
adShow.value = true;
});
// 监听广告加载失败
bannerAd.onError((err) => {
console.error('Banner广告加载失败:', err);
adShow.value = false;
});
// 监听广告被点击
bannerAd.onClose(() => {
console.log('Banner广告被关闭');
});
};
</script>
<style scoped>
.banner-ad-container {
width: 100%;
height: 100rpx;
margin-top: 20rpx;
}
/* 省略其他样式... */
</style>
6.6.3 插屏广告集成
插屏广告在页面切换或特定操作后弹出,覆盖部分页面内容,集成步骤如下:
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
import { useRouter } from 'uni-app';
const router = useRouter();
let interstitialAd: WechatMiniprogram.InterstitialAd | null = null;
// 页面加载时创建插屏广告
onMounted(() => {
createInterstitialAd();
});
// 页面卸载时销毁广告
onUnmounted(() => {
if (interstitialAd) {
interstitialAd.destroy();
interstitialAd = null;
}
});
// 创建插屏广告
const createInterstitialAd = () => {
// 替换为自己的插屏广告位ID
const adUnitId = 'adunit-yyyyyyyyyyyyyyyy';
// 判断微信基础库是否支持插屏广告
if (uni.createInterstitialAd) {
interstitialAd = uni.createInterstitialAd({ adUnitId });
// 监听广告加载成功
interstitialAd.onLoad(() => {
console.log('插屏广告加载成功');
});
// 监听广告加载失败
interstitialAd.onError((err) => {
console.error('插屏广告加载失败:', err);
});
// 监听广告被关闭
interstitialAd.onClose((res) => {
console.log('插屏广告被关闭', res);
// 广告关闭后可执行后续操作(如跳转页面)
if (res && res.isEnded) {
// 广告播放完成后关闭
router.push('/pages/detail/detail');
} else {
// 广告未播放完成被关闭
uni.showToast({ title: '广告未完成播放', icon: 'none' });
}
});
}
};
// 触发插屏广告显示(如点击按钮后)
const showInterstitialAd = () => {
if (interstitialAd) {
interstitialAd.show().catch((err) => {
console.error('插屏广告显示失败:', err);
// 显示失败时重新加载广告
interstitialAd.load().then(() => {
interstitialAd.show();
});
});
}
};
</script>
6.6.4 注意事项
-
广告位ID需正确配置,测试环境可使用微信提供的测试广告位ID
-
广告加载和显示需处理异常情况(如网络错误、广告加载失败),避免影响用户体验
-
广告展示频率需合理,避免过度推送导致用户反感(遵循微信流量主规范)
-
禁止对广告进行遮挡、篡改,否则可能被取消流量主资格
-
激励视频广告(用户观看后获得奖励)需额外配置奖励发放逻辑,可参考微信官方文档
七、项目打包与发布
7.1 打包小程序代码
将Uni-app项目编译为微信小程序生产环境代码:
-
打开HBuilderX,右键项目根目录,选择「发行」-「小程序-微信」
-
在弹出的打包配置窗口中,填写「小程序名称」「小程序AppID」(需与manifest.json一致),勾选「压缩代码」「运行时压缩」(生产环境推荐),点击「发行」
-
等待打包完成,HBuilderX会生成生产环境的小程序代码(默认路径:unpackage/dist/build/mp-weixin)
7.2 小程序预览与调试
在微信开发者工具中预览打包后的代码,确保功能正常:
-
打开微信开发者工具,点击「导入项目」,选择打包后的mp-weixin文件夹,点击「导入」
-
点击「预览」按钮,用微信扫码查看小程序效果,测试所有功能(登录、跳转、接口请求等)
-
若发现问题,在微信开发者工具中查看控制台日志(Console),定位问题后回到HBuilderX修改代码,重新打包预览
7.3 小程序提交审核与发布
功能测试无误后,提交审核并发布:
-
在微信开发者工具中,点击「上传」按钮,填写「版本号」(如1.0.0)、「项目备注」(如首次提交),点击「上传」
-
上传成功后,登录微信小程序后台:https://mp.weixin.qq.com/,进入「版本管理」-「开发版本」,找到刚上传的版本,点击「提交审核」
-
填写审核信息:选择「服务类目」(需与小程序功能一致)、填写「小程序介绍」「测试账号(可选)」,点击「提交」
-
等待微信审核(通常1-3个工作日),审核通过后,进入「版本管理」-「审核版本」,点击「发布」,小程序即可上线
八、常见问题解决
8.1 小程序登录失败(code无效)
-
检查小程序AppID是否正确(开发环境与生产环境AppID需区分)
-
确保后端接口正确处理code,code有效期为5分钟,需及时换取openid
-
检查开发者账号是否已添加到小程序后台的「成员管理」中
8.2 接口请求失败(跨域/403)
-
小程序端需在后台配置「合法域名」:进入小程序后台「开发」-「开发设置」-「服务器域名」,添加接口域名(需备案)
-
开发环境可勾选微信开发者工具「设置」-「项目设置」-「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」(仅开发用)
-
检查接口请求头是否携带必要参数(如token),后端是否正确验证
8.3 打包后小程序体积过大(超过2M)
-
使用Uni-app分包加载:在pages.json中配置subPackages,将非首页的页面拆分到分包中(小程序主包体积限制2M,分包总和限制20M)
-
压缩静态资源:图片用tinypng压缩,优先使用WebP格式,大图片使用CDN加载
-
删除无用代码与依赖:清理未使用的组件、插件,减少项目体积
8.4 页面跳转失败(路由错误)
-
检查pages.json中是否配置了目标页面的路由路径,路径是否正确(区分相对路径与绝对路径)
-
确保跳转时携带的参数格式正确(如id为数字类型,避免字符串格式错误)
-
tabBar页面只能用uni.switchTab跳转,不能用uni.navigateTo跳转
九、总结
本教程从零完成了Uni-app+微信小程序项目的搭建与落地,核心流程为:环境准备→项目创建→核心配置(路由、请求、登录)→功能开发→打包发布。依托Uni-app的跨端能力,开发者无需学习小程序原生语法,即可快速开发小程序,同时代码可复用至H5、App等其他端。
如需进一步扩展功能,可参考微信小程序官方文档(https://developers.weixin.qq.com/miniprogram/dev/framework/)和Uni-app官方文档(https://uniapp.dcloud.net.cn/),学习更多高级功能(如小程序支付、推送、地图等)。
更多推荐
所有评论(0)