uniapp 从入门到精通:第5章 5.1 微信小程序原生能力

🎯 开场:为什么你写的 App 总是"差点意思"?

上一章我们搞定了电商首页和商品详情,页面漂漂亮亮的,用户也能下单了。但你有没有这种感觉——

"页面是有了,但总像个空壳子,不像一个真正的 App"

比如:
- 别人的小程序能一键微信登录,你的只能输账号密码
- 别人能分享商品到朋友圈,你点分享没反应
- 别人的 App 能调起微信支付,你的只能"请联系客服"

这些不是页面问题,是 原生能力 没接上!

说白了:页面是皮,原生能力是骨。没有骨头,皮再漂亮也是个软脚虾。

本章学完,你能:
1. 让用户一键微信登录(不用记密码)
2. 实现分享功能(让用户帮你拉新)
3. 摸到支付的门把手(下一章会细讲)


🧱 基础:UniApp 原生能力是个啥?

5.1.1 什么是原生能力?

先打个比方:

你的小程序就像一家餐厅
- 前端页面 = 餐厅的装修和菜单(好看很重要)
- 原生能力 = 厨房设备\n\nSimple tech illustration expla\n\nAI comic creation scene, creat\n\n + 服务员(能上菜、能接待、能收款)

UniApp 本身是个"通用厨房",但微信小程序有自己独家的设备:
- 微信登录(只用微信的人,你的 App 也能用他的微信账号)
- 微信支付(收钱用的)
- 分享到微信群/朋友圈(免费推广)
- 获取用户微信头像昵称(不用用户再填)

5.1.2 UniApp 怎么调用原生能力?

UniApp 封装了一套 uni.xxx API,不管你打包到哪个平台,都能用同一套代码。

但!有些能力只有微信小程序支持,这时候就要用条件编译——

// 微信小程序专属能力
#ifdef MP-WEIXIN
uni.login({
success: (res) => {
console.log('登录成功', res.code)
}
})
#endif

这段代码的意思是:只有打包成微信小程序时才编译这段代码,其他平台跳过。

简单理解:UniApp 是个翻译官,原生能力是方言。有些话(通用 API)它能翻所有平台;有些话(微信专属)只有翻译成"微信话"才有人听得懂。

5.1.3 四大金刚:login / getUserProfile / share / payment

UniApp 调用微信原生能力,主要靠这 4 个:

API 干啥的 类比
uni.login() 获取微信登录凭证 拿身份证号(临时证明你是微信用户)
uni.getUserProfile() 获取用户头像昵称 查户口本
uni.share() 分享内容 发传单
uni.requestPayment() 微信支付 刷卡收款

🔥 实战:三个小项目搞定原生能力

项目 1:一键微信登录(5 分钟)

场景:用户打开 App,直接弹出微信登录,用户授权后显示头像。

// pages/index/index.vue
<template>
<view class="container">
<!-- 未登录状态 -->
<view v-if="!hasLogin" class="login-box">
  <button type="primary" @click="handleWxLogin">微信一键登录</button>
</view>

<!-- 已登录状态 -->
<view v-else class="user-info">
  <image class="avatar" :src="userInfo.avatarUrl"></image>
  <text class="nickname">{{ userInfo.nickName }}</text>
  <text class="welcome">欢迎回来!</text>
</view>
</view>
</template>

<script>
export default {
data() {
return {
  hasLogin: false,
  userInfo: {
    nickName: '',
    avatarUrl: ''
  }
}
},
methods: {
// 微信登录核心代码
handleWxLogin() {
  // 第一步:获取登录凭证
  uni.login({
    provider: 'weixin',  // 明确指定微信
    success: (loginRes) => {
      console.log('登录凭证:', loginRes.code)  // 临时凭证,发给后端换 token

      // 第二步:获取用户信息(头像昵称)
      uni.getUserProfile({
        desc: '用于展示用户头像和昵称',  // 必填!微信审核要看的
        success: (profileRes) => {
          console.log('用户信息:', profileRes.userInfo)

          this.userInfo = profileRes.userInfo
          this.hasLogin = true

          // 这里应该调用后端接口,用 code 换真正的 token
          // this.getTokenFromBackend(loginRes.code)
        },
        fail: () => {
          uni.showToast({ title: '需要授权才能登录', icon: 'none' })
        }
      })
    },
    fail: () => {
      uni.showToast({ title: '微信登录失败', icon: 'none' })
    }
  })
}
}
}
</script>

<style>
.container { padding: 40rpx; }
.login-box { margin-top: 200rpx; text-align: center; }
.user-info { text-align: center; margin-top: 200rpx; }
.avatar { width: 160rpx; height: 160rpx; border-radius: 50%; }
.nickname { display: block; font-size: 36rpx; margin: 20rpx 0; }
.welcome { color: #999; }
</style>

运行效果
- 点击按钮 → 弹出微信授权框 → 用户点"允许" → 显示头像和昵称

这代码干了啥
- uni.login() 拿到一个临时 code,发给后端换真正的登录态
- uni.getUserProfile() 获取用户头像昵称(用户必须手动点"允许")

注意!2023 年后微信改了规则,getUserProfile 必须用户主动触发,不能自动弹!别想着偷偷获取用户信息了。


项目 2:商品详情页分享(15 分钟)

场景:用户看完商品详情,点分享按钮,能把商品卡片分享到微信群/朋友圈。

// pages/goods-detail/goods-detail.vue
<template>
<view class="detail-page">
<image class="goods-img" :src="goods.image"></image>
<view class="goods-info">
  <text class="title">{{ goods.name }}</text>
  <text class="price">¥{{ goods.price }}</text>
  <text class="desc">{{ goods.description }}</text>
</view>

<!-- 分享按钮 -->
<button class="share-btn" plain @click="handleShare">分享给好友</button>
</view>
</template>

<script>
export default {
data() {
return {
  goods: {
    id: '001',
    name: '阳光玫瑰葡萄 2斤装',
    price: 59.9,
    description: '当季新鲜水果,颗颗饱满,香甜多汁',
    image: '/static/grape.jpg'
  }
}
},
onShareAppMessage() {
// 配置分享内容(右上角三点菜单的分享)
return {
  title: this.goods.name,           // 分享标题
  desc: this.goods.description,      // 分享描述
  imageUrl: this.goods.image,        // 分享封面图
  path: `/pages/goods-detail/goods-detail?id=${this.goods.id}`  // 点开后的页面
}
},
onShareTimeline() {
// 配置分享到朋友圈
return {
  title: `【限时特惠】${this.goods.name} 仅需¥${this.goods.price}`,
  query: `id=${this.goods.id}`  // 携带商品 ID
}
},
methods: {
handleShare() {
  // 调起内置分享面板
  uni.showShareMenu({
    withShareTicket: true,  // 带上群票据(可用于判断分享到哪个群)
    menus: ['shareAppMessage', 'shareTimeline']  // 分享给好友 / 分享到朋友圈
  })
  // 注意:实际调起分享是用户点右上角...菜单,这里只是开启开关
}
}
}
</script>

<style>
.detail-page { padding: 30rpx; }
.goods-img { width: 100%; height: 500rpx; border-radius: 16rpx; }
.goods-info { padding: 30rpx 0; }
.title { font-size: 36rpx; font-weight: bold; display: block; }
.price { color: #ff4d4f; font-size: 40rpx; display: block; margin: 20rpx 0; }
.desc { color: #666; line-height: 1.6; }
.share-btn { margin-top: 60rpx; }
</style>

运行效果
- 用户点按钮 → 开启分享菜单
- 用户点右上角「...」→「发送给朋友」→ 好友看到商品卡片
- 用户点右上角「...」→「分享到朋友圈」→ 朋友圈显示商品信息

关键点解释
- onShareAppMessage = 监听"发送给好友"事件,返回分享配置
- onShareTimeline = 监听"分享到朋友圈"事件
- uni.showShareMenu() = 在代码里主动开启分享按钮(默认可能不显示)

说白了:分享就是"帮你发传单"。你把传单内容(标题、图片、链接)准备好,微信帮你发。


项目 3:购物车订单汇总生成器(15 分钟)

场景:用户把商品加入购物车后,生成一份订单汇总,可以分享给朋友帮忙付款。

// pages/cart/cart.vue
<template>
<view class="cart-page">
<!-- 购物车列表 -->
<checkbox-group @change="onCheckChange">
  <view v-for="item in cartItems" :key="item.id" class="cart-item">
    <checkbox :value="item.id" :checked="item.checked"></checkbox>
    <text class="item-name">{{ item.name }}</text>
    <text class="item-price">¥{{ item.price }} × {{ item.num }}</text>
  </view>
</checkbox-group>

<!-- 订单汇总 -->
<view class="order-summary">
  <text>共 {{ selectedCount }} 件商品</text>
  <text class="total-price">合计:¥{{ totalPrice }}</text>
  <button type="warn" size="mini" @click="generateOrderShare">生成付款链接</button>
</view>
</view>
</template>

<script>
export default {
data() {
return {
  cartItems: [
    { id: '1', name: '阳光玫瑰葡萄', price: 59.9, num: 2, checked: true },
    { id: '2', name: '有机草莓', price: 35.0, num: 1, checked: true },
    { id: '3', name: '智利车厘子', price: 88.0, num: 1, checked: false }
  ]
}
},
computed: {
selectedItems() {
  return this.cartItems.filter(item => item.checked)
},
selectedCount() {
  return this.selectedItems.reduce((sum, item) => sum + item.num, 0)
},
totalPrice() {
  return this.selectedItems.reduce((sum, item) => sum + item.price * item.num, 0).toFixed(2)
}
},
methods: {
onCheckChange(e) {
  const selected = e.detail.value
  this.cartItems.forEach(item => {
    item.checked = selected.includes(item.id)
  })
},

// 生成订单分享信息
generateOrderShare() {
  if (this.selectedCount === 0) {
    uni.showToast({ title: '请先选择商品', icon: 'none' })
    return
  }

  const orderData = {
    items: this.selectedItems.map(i => `${i.name}×${i.num}`).join('、'),
    total: this.totalPrice,
    time: new Date().toLocaleDateString()
  }

  // 方式1:生成分享数据(点开小程序查看订单)
  uni.showShareMenu({
    withShareTicket: true,
    menus: ['shareAppMessage', 'shareTimeline']
  })

  uni.showToast({ 
    title: '已生成链接,可分享给好友', 
    icon: 'success' 
  })

  // 方式2:复制订单信息(用于微信外传播)
  uni.setClipboardData({
    data: `【订单汇总】${orderData.items}\n合计:¥${orderData.total}\n下单时间:${orderData.time}`,
    success: () => {
      uni.showToast({ title: '订单已复制,去分享吧', icon: 'none' })
    }
  })

  // 实际项目中,这里应该调用后端生成真实订单,返回订单号
  console.log('当前订单汇总:', orderData)
}
},
onShareAppMessage() {
const selected = this.selectedItems
if (selected.length === 0) return {}

return {
  title: `帮我付款!订单合计¥${this.totalPrice}`,
  desc: `订单包含:${selected.map(i => i.name).join('、')}`,
  path: '/pages/cart/cart?from=share'
}
}
}
</script>

<style>
.cart-page { padding: 30rpx; }
.cart-item { display: flex; align-items: center; padding: 20rpx 0; border-bottom: 1rpx solid #eee; }
.item-name { flex: 1; margin-left: 20rpx; }
.item-price { color: #ff4d4f; }
.order-summary { 
position: fixed; bottom: 0; left: 0; right: 0; 
display: flex; align-items: center; padding: 30rpx; 
background: #fff; box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.1);
}
.total-price { flex: 1; text-align: right; color: #ff4d4f; font-size: 36rpx; font-weight: bold; margin-right: 20rpx; }
</style>

运行效果
- 用户勾选商品 → 显示合计价格
- 点击「生成付款链接」→ 复制订单文字 → 分享到微信

这代码干了啥
- computed 计算选中的商品和总价
- uni.setClipboardData() 把订单复制到剪贴板(方便微信外传播)
- onShareAppMessage 分享到微信好友时显示订单信息

例子:就像你去奶茶店点单,店员说"要不要生成订单链接发给朋友,让他帮你付?"——这就是把付款能力分享出去。


💪 进阶:踩坑 + 调试技巧

坑 1:getUserProfile 调不通?授权描述没写!

// ❌ 错误:desc 参数缺失,直接报错
uni.getUserProfile({
success: (res) => { /* ... */ }
})

// ✅ 正确:desc 是必填项,要写清楚用途
uni.getUserProfile({
desc: '用于完善会员资料',  // 必须写!不然微信直接拒绝
success: (res) => { /* ... */ }
})

坑 2:分享图片不显示?尺寸不对!

// ❌ 错误:用了本地图片(分享时图片不展示)
imageUrl: '/static/grape.jpg'

// ✅ 正确:分享图片要用线上地址,或者用 canvas 生成分享图
imageUrl: 'https://your-server.com/images/grape.jpg'

坑 3:onShareAppMessage 不生效?检查 pages.json!

// pages.json - 开启当前页面的分享功能
{
"pages": [
{
  "path": "pages/goods-detail/goods-detail",
  "style": {
    "enableShareAppMessage": true,   // 开启分享给好友
    "enableShareTimeline": true      // 开启分享到朋友圈
  }
}
]
}

坑 4:login 的 code 只能一次性使用!

// ❌ 错误:同一个 code 用了两次
uni.login({
success: (res) => {
myRequest1(res.code)  // 用 code 换 token
myRequest2(res.code)  // code 已经失效了!
}
})

// ✅ 正确:code 只能用一次,要用就得重新 login
let currentCode = null
uni.login({
success: (res) => {
currentCode = res.code
myRequest1(currentCode)  // 这次用的是同一个 code
}
})
// 下次分享时重新获取 code

坑 5:支付回调可能是假的!

微信支付成功后,success 回调只是"前端发起成功",不等同于真正支付成功。后端必须收到微信的支付回调通知才算数。

// ❌ 错误:前端回调就确认收款
uni.requestPayment({
success: () => {
this.updateOrderStatus('已支付')  // 不安全!可能是假的
}
})

// ✅ 正确:以后端回调为准,前端只是引导用户
uni.requestPayment({
success: () => {
uni.showToast({ title: '支付成功,等待确认...', icon: 'none' })
},
fail: () => {
uni.showToast({ title: '支付取消', icon: 'none' })
}
})

调试技巧:看日志的正确方式

// 基础:console.log(开发时用)
console.log('login 结果:', res)

// 进阶:uni.showToast(看运行时数据)
uni.showToast({ title: JSON.stringify(res), icon: 'none', duration: 3000 })

// 生产环境:封装日志请求,发送到服务器
function logToServer(msg) {
// 实际项目里应该发到自己的日志服务
console.warn('[日志]', msg)
}

✏️ 练习题

练习 1(2 分钟):改改登录提示语
- 输入:在项目 1 中,把「需要授权才能登录」的提示改成「拒绝授权就无法使用哦」
- 预期输出:点击拒绝后,toast 显示新提示语
- 提示:找到 fail 回调里的 title 参数

练习 2(3 分钟):加个登录条件
- 输入:在项目 1 中,只有用户昵称包含「小」字才显示「欢迎回来」,否则显示「很高兴认识你」
- 预期输出:不同昵称显示不同欢迎语
- 提示:用 if 判断 this.userInfo.nickName.includes('小')

练习 3(10 分钟):换个商品数据
- 输入:用练习题 2 的登录代码 + 项目 2 的分享代码,实现:登录后用户可分享自己选中的商品
- 预期输出:分享出去的卡片显示用户头像 + 商品信息
- 提示:onShareAppMessage 里可以访问 this.userInfo

练习 4(15 分钟):购物车勾选反选
- 输入:在项目 3 中,添加一个「全选/取消全选」按钮
- 预期输出:点全选所有商品被勾选,再点取消全选
- 提示:给全选按钮绑定事件,遍历 cartItems 设置 checked 状态

练习 5(5 分钟):看图找错
- 输入:以下代码点击分享后,分享出去的卡片没有图片,是哪里的问题?

onShareAppMessage() {
return {
title: '超好吃的葡萄',
imageUrl: '/static/grape.png',  // 本地图片
path: '/pages/index/index'
}
}
  • 预期输出:说出原因并给出解决方案
  • 提示:参考进阶坑 2

作业:做一个「商品测评分享工具」

小明想在朋友圈卖自家种的苹果,需要一个分享工具。

需求描述
用户输入苹果的品种、价格、口感描述,选择苹果图片,生成一个精美的分享卡片,可以分享到朋友圈。

功能点
1. 表单输入:品种(文本)、价格(数字)、口感描述(多行文本)
2. 图片选择:调用 uni.chooseImage 选择苹果照片
3. 分享功能:点击分享,生成带图分享到朋友圈

加分项
1. 添加「预览」按钮,先看效果再分享
2. 记录历史分享记录(存本地 uni.setStorage

验收标准
- 能输入表单并选择图片
- 点击分享后,朋友圈能看到带图分享
- 代码有适当注释


📚 总结 + 资源

本文学了 3 件事
1. uni.login + uni.getUserProfile 实现微信一键登录
2. onShareAppMessage + onShareTimeline 实现分享到好友/朋友圈
3. 条件编译 <!-- #ifdef MP-WEIXIN --> 让代码只在微信小程序生效

延伸学习
- UniApp 官方文档 - 微信小程序能力
- 微信小程序分享文档
- 《UniApp 跨平台开发实战》相关章节

互动钩子
你在做微信登录时,被用户拒绝过授权吗?怎么处理的?评论区聊聊老粉优先回复!


彩蛋预告:下一章我们要解决一个大问题——「小程序太大了加载慢怎么办?」学会分包加载,让你的 App 启动快到飞起!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。