一、实现原理
- 生成阶段:为每个物品生成唯一参数(如itemId)的小程序码
- 解析阶段:用户扫码后解析参数
- 跳转阶段:携带参数跳转至详情页
- 数据获取:根据参数获取对应数据
二、完整实现代码
1. 生成带参小程序码(后端示例)
// 以Node.js为例(需先获取access_token)
const axios = require('axios');
async function generateQrcode(itemId) {
const access_token = await getAccessToken();
const response = await axios.post(
`https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${access_token}`,
{
scene: `itemId=${itemId}`,
page: 'pages/index/index',
width: 430
},
{ responseType: 'arraybuffer' }
);
return response.data;
}
2. App.vue处理启动参数
// App.vue
export default {
onLaunch(options) {
this.handleLaunchParams(options);
},
methods: {
handleLaunchParams(options) {
let sceneParams = {};
// 微信小程序场景值处理
if (options.query.scene) {
sceneParams = this.parseScene(decodeURIComponent(options.query.scene));
}
// 处理App端参数
if (plus && plus.runtime.arguments) {
sceneParams = this.parseScene(plus.runtime.arguments);
}
if (sceneParams.itemId) {
uni.reLaunch({
url: `/pages/detail/detail?itemId=${sceneParams.itemId}`
});
}
},
parseScene(sceneStr) {
const params = {};
sceneStr.split('&').forEach(pair => {
const [key, value] = pair.split('=');
if (key) params[key] = value || '';
});
return params;
}
}
}
3. 详情页实现
<!-- pages/detail/detail.vue -->
<template>
<view class="container">
<view v-if="loading" class="loading">加载中...</view>
<view v-else-if="error" class="error">{{ error }}</view>
<view v-else>
<image :src="item.image" mode="aspectFit" />
<view class="title">{{ item.name }}</view>
<view class="info">{{ item.description }}</view>
<view class="meta">生产日期:{{ item.productionDate }}</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
itemId: '',
item: null,
loading: true,
error: ''
};
},
onLoad(query) {
this.itemId = query.itemId;
if (!this.itemId) {
this.error = '无效的产品编码';
this.loading = false;
return;
}
this.loadItemDetail();
},
methods: {
async loadItemDetail() {
try {
const res = await uni.request({
url: '/api/item/detail',
data: { itemId: this.itemId }
});
if (res.data.code === 200) {
this.item = res.data.result;
} else {
this.error = '产品信息获取失败';
}
} catch (e) {
this.error = '网络请求失败';
console.error(e);
} finally {
this.loading = false;
}
}
}
};
</script>
<style>
.container { padding: 20rpx; }
.loading, .error { text-align: center; padding: 40rpx; }
.title { font-size: 36rpx; font-weight: bold; margin: 20rpx 0; }
.info { font-size: 28rpx; color: #666; }
.meta { margin-top: 40rpx; color: #999; }
</style>
4. 主动扫码功能实现
<!-- pages/scan/scan.vue -->
<template>
<view class="scan-container">
<button @click="handleScan" class="scan-btn">扫码验证</button>
<view class="tip">点击按钮扫描产品二维码</view>
</view>
</template>
<script>
export default {
methods: {
handleScan() {
uni.scanCode({
onlyFromCamera: true,
scanType: ['qrCode'],
success: (res) => {
const params = this.parseScene(res.result);
if (params.itemId) {
uni.navigateTo({
url: `/pages/detail/detail?itemId=${params.itemId}`
});
} else {
uni.showToast({ title: '无效的二维码', icon: 'none' });
}
},
fail: (err) => {
uni.showToast({ title: '扫码失败', icon: 'none' });
}
});
},
parseScene(sceneStr) {
const params = {};
// 处理多种可能的格式:
// 1. 纯参数形式:itemId=123
// 2. URL形式:https://...?itemId=123
const queryString = sceneStr.includes('?')
? sceneStr.split('?')[1]
: sceneStr;
queryString.split('&').forEach(pair => {
const [key, value] = pair.split('=');
if (key) params[key] = decodeURIComponent(value || '');
});
return params;
}
}
};
</script>
<style>
.scan-container { text-align: center; padding-top: 100rpx; }
.scan-btn { width: 60%; margin: 40rpx auto; }
.tip { color: #888; font-size: 24rpx; }
</style>
5. 生成二维码页面
<!-- pages/generate/generate.vue -->
<template>
<view class="generate-container">
<input v-model="itemId" placeholder="输入产品ID" class="input" />
<button @click="generate" class="generate-btn">生成二维码</button>
<canvas v-if="showCanvas" canvas-id="qrcode" class="qrcode-canvas" />
<image v-if="qrcodeUrl" :src="qrcodeUrl" class="qrcode-image" />
</view>
</template>
<script>
import uQRCode from '@/static/uqrcode.js';
export default {
data() {
return {
itemId: '',
qrcodeUrl: '',
showCanvas: false
};
},
methods: {
async generate() {
if (!this.itemId) {
uni.showToast({ title: '请输入产品ID', icon: 'none' });
return;
}
// 生成小程序码(需配置云函数或替换为自己的API)
const res = await uni.request({
url: '/api/generateQrcode',
method: 'POST',
data: { itemId: this.itemId }
});
if (res.data.code === 200) {
this.qrcodeUrl = res.data.qrcodeUrl;
} else {
// 本地生成二维码备选方案
this.generateLocalQrcode();
}
},
generateLocalQrcode() {
this.showCanvas = true;
this.$nextTick(() => {
uQRCode.make({
canvasId: 'qrcode',
text: `itemId=${this.itemId}`,
size: 300,
margin: 10,
success: res => {
this.qrcodeUrl = res;
}
});
});
}
}
};
</script>
<style>
.generate-container { padding: 40rpx; }
.input { border: 1px solid #ddd; padding: 20rpx; margin-bottom: 40rpx; }
.generate-btn { margin-bottom: 40rpx; }
.qrcode-canvas, .qrcode-image {
width: 300px;
height: 300px;
margin: 0 auto;
display: block;
}
</style>
三、关键配置说明
-
微信公众平台配置:
- 登录小程序后台
- 开发设置 → 服务器域名 → 添加request合法域名
- 开发设置 → 普通链接二维码打开小程序 → 添加业务域名
-
安全注意事项:
- 验证参数合法性
- 接口请求需要签名验证
- 敏感操作需要添加验证码
- 重要接口限制调用频率
-
性能优化建议:
- 添加数据缓存机制
- 使用webp格式图片
- 实现分页加载
- 添加骨架屏效果
四、扩展功能建议
- 溯源信息:添加生产、物流信息时间轴
- 防伪验证:添加官方验证提示
- 数据统计:记录扫码时间、位置等信息
- 过期验证:添加二维码有效期验证
- 分享功能:实现带参分享卡片
以上实现方案完整覆盖了一物一码的核心流程,可根据实际业务需求进行扩展和调整。建议在正式环境中添加错误监控和日志记录功能,便于问题排查和系统优化。