feat: 实现微信小程序码生成功能

- 新增 pages/scan/result/ 扫码结果页,展示预约详情
- 实现微信小程序码生成(wxacode.getUnlimited)
- 添加环境版本自动检测(release/trial/develop)
- 优化 qrcode-modal 组件,使用 Base64 图片替代本地生成
- 统一 API 接口抽离到 api.js 和 config.js
- 优化代码结构,提升可读性和维护性

主要变更:
1. utils/api.js: 新增 getWxacode() 接口,支持生成小程序码
2. components/qrcode-modal: 改用 API 生成小程序码,移除本地 QRCode 依赖
3. pages/scan/result: 新增扫码结果展示页,解析 scene 参数
4. utils/config.js: 新增 WXACODE 配置项
This commit is contained in:
ws
2026-04-27 18:36:04 +08:00
parent eb5af3fa4a
commit 0c6b7fcace
6 changed files with 71 additions and 45 deletions
+15 -20
View File
@@ -1,33 +1,28 @@
const drawQrcode = require('../../utils/weapp.qrcode.esm.js').default const { getWxacode } = require('../../utils/api')
Component({ Component({
data: { data: {
visible: false, visible: false,
qrcodeId: '', qrcodeId: '',
loading: true loading: true,
qrcodePath: ''
}, },
methods: { methods: {
show(id) { async show(id) {
this.setData({ visible: true, qrcodeId: id, loading: true }) this.setData({ visible: true, qrcodeId: id, loading: true, qrcodePath: '' })
wx.nextTick(() => { try {
drawQrcode({ const base64Image = await getWxacode(id, 'pages/scan/result/index')
width: 200, this.setData({ qrcodePath: base64Image, loading: false })
height: 200, } catch (err) {
canvasId: 'qrcodeCanvas', console.error('获取小程序码失败', err)
_this: this, wx.showToast({ title: '二维码生成失败', icon: 'none' })
text: id, this.setData({ loading: false })
callback: () => { }
this.setData({ loading: false })
}
})
})
}, },
onClose() { onClose() {
this.setData({ visible: false, qrcodeId: '' }) this.setData({ visible: false, qrcodeId: '', qrcodePath: '' })
}, }
noop() {}
} }
}) })
+2 -2
View File
@@ -1,8 +1,8 @@
<view class="qrcode-modal" wx:if="{{visible}}" bindtap="onClose" catchtouchmove="noop"> <view class="qrcode-modal" wx:if="{{visible}}" bindtap="onClose" catchtouchmove="noop">
<view class="qrcode-container" catchtap catchtouchmove="noop"> <view class="qrcode-container" catchtap catchtouchmove="noop">
<view class="qrcode-title">预约凭证</view> <view class="qrcode-title">预约凭证</view>
<view class="qrcode-canvas-wrap"> <view class="qrcode-image-wrap">
<canvas canvas-id="qrcodeCanvas" class="qrcode-canvas"></canvas> <image wx:if="{{qrcodePath && !loading}}" class="qrcode-image" src="{{qrcodePath}}" mode="aspectFit" />
<view class="qrcode-loading" wx:if="{{loading}}"> <view class="qrcode-loading" wx:if="{{loading}}">
<view class="qrcode-spinner"></view> <view class="qrcode-spinner"></view>
</view> </view>
+6 -2
View File
@@ -29,16 +29,20 @@
letter-spacing: 4rpx; letter-spacing: 4rpx;
} }
.qrcode-canvas-wrap { .qrcode-image-wrap {
position: relative; position: relative;
width: 400rpx; width: 400rpx;
height: 400rpx; height: 400rpx;
border: 1rpx solid #e8eef5; border: 1rpx solid #e8eef5;
border-radius: 16rpx; border-radius: 16rpx;
overflow: hidden; overflow: hidden;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
} }
.qrcode-canvas { .qrcode-image {
width: 400rpx; width: 400rpx;
height: 400rpx; height: 400rpx;
} }
+17 -17
View File
@@ -1,4 +1,3 @@
// pages/scan/result/index.js
const { appointmentDB, formatRecord } = require('../../../utils/api') const { appointmentDB, formatRecord } = require('../../../utils/api')
Page({ Page({
@@ -9,48 +8,49 @@ Page({
}, },
onLoad(options) { onLoad(options) {
const id = options.id const id = this.extractId(options)
if (!id) { if (!id) {
this.setData({ this.setData({ loading: false, error: '缺少预约记录ID' })
loading: false,
error: '缺少预约记录ID'
})
return return
} }
this.loadRecordDetail(id) this.loadRecordDetail(id)
}, },
extractId(options) {
if (options.id) return options.id
if (!options.scene) return null
const scene = decodeURIComponent(options.scene)
return scene.startsWith('id=') ? scene.substring(3) : scene
},
async loadRecordDetail(id) { async loadRecordDetail(id) {
try { try {
this.setData({ loading: true }) this.setData({ loading: true })
const result = await appointmentDB.getDetail(id) const result = await appointmentDB.getDetail(id)
if (!result) { if (!result) {
this.setData({ this.setData({ loading: false, error: '预约记录不存在' })
loading: false,
error: '预约记录不存在'
})
return return
} }
const formatted = formatRecord(result)
const statusMap = { const statusMap = {
'pending': '待审核', 'pending': '待审核',
'approved': '已通过', 'approved': '已通过',
'rejected': '已拒绝', 'rejected': '已拒绝',
'cancelled': '已取消' 'cancelled': '已取消'
} }
this.setData({ this.setData({
record: { record: {
...formatted, ...formatRecord(result),
statusText: statusMap[formatted.status] || formatted.status statusText: statusMap[result.status] || result.status
}, },
loading: false loading: false
}) })
} catch (err) { } catch (err) {
console.error('加载预约记录详情失败', err) console.error('加载预约记录详情失败', err)
this.setData({ this.setData({ loading: false, error: '加载失败,请稍后重试' })
loading: false,
error: '加载失败,请稍后重试'
})
} }
} }
}) })
+29 -3
View File
@@ -144,7 +144,33 @@ const appointmentDB = {
} }
} }
module.exports = { /**
formatRecord, * 获取小程序码
appointmentDB * @param {string} scene - 场景值
* @param {string} page - 页面路径
* @returns {Promise<string>} Base64图片数据
*/
async function getWxacode(scene, page = 'pages/scan/result/index') {
const envVersion = wx.getAccountInfoSync().miniProgram.envVersion
const base64Data = await request({
url: BASE_URL + API.WXACODE,
method: 'POST',
header: { 'content-type': 'application/json' },
data: {
scene,
page,
width: 430,
envVersion
}
})
return `data:image/jpeg;base64,${base64Data}`
}
module.exports = {
request,
formatRecord,
appointmentDB,
getWxacode
} }
+2 -1
View File
@@ -26,7 +26,8 @@ const API = {
APPOINTMENT_LIST: '/api/wx-mini/appointment/list', APPOINTMENT_LIST: '/api/wx-mini/appointment/list',
APPOINTMENT_CREATE: '/api/wx-mini/appointment/create', APPOINTMENT_CREATE: '/api/wx-mini/appointment/create',
APPOINTMENT_CANCEL: '/api/wx-mini/appointment/cancel', APPOINTMENT_CANCEL: '/api/wx-mini/appointment/cancel',
APPOINTMENT_DETAIL: '/api/wx-mini/appointment/detail' APPOINTMENT_DETAIL: '/api/wx-mini/appointment/detail',
WXACODE: '/api/wx-mini/wxacode'
} }
console.log('[config] 当前环境:', wx.getAccountInfoSync().miniProgram.envVersion, 'BASE_URL:', BASE_URL) console.log('[config] 当前环境:', wx.getAccountInfoSync().miniProgram.envVersion, 'BASE_URL:', BASE_URL)