简介:开箱即用的礼品卡提货系统,支持H5、微信小程序、App三端运行,基于Vue 2与Vue 3双版本兼容架构,后端依托uniCloud云开发平台。前端包含69个核心JS工具脚本(含表单验证validate.js、云数据库操作clientdb.js、用户反馈opendb-feedback.js等),56个可复用Vue组件,13个SCSS样式文件及配套图标资源(PNG、字体图标、icons.js)。已集成uniCloud数据库增删改查、数据联动选择器(uni-data-picker.js)、权限控制基础结构、用户反馈模块和完整表单校验逻辑。配置文件齐全(pages.、manifest.、vue.config.js等),附带changelog.md变更日志与license.md开源协议说明。适用于企业礼品卡发放、核销、库存查询与用户自助提货等真实业务流程,支持一键部署、本地调试及二次开发定制。
1. 这不是Demo,是能直接进生产环境的礼品卡提货系统
我去年帮一家做节日礼盒定制的客户上线过一套礼品卡核销系统,当时他们用的是某SaaS平台的模板,结果每到春节前订单暴增时,小程序就频繁报“网络错误”,客服电话被打爆,最后发现是后端接口扛不住并发——单次核销要查用户、验卡密、扣库存、写日志、发通知,五步串行调用,平均响应超2.8秒。后来我们彻底重做了整套流程,用的就是现在这套源码包的底层架构。它不是教学Demo,也不是功能残缺的“骨架项目”,而是一套经过真实业务锤炼、多轮压测、三端并行交付的完整解决方案。
核心关键词你已经看到了:礼品卡提货、uniCloud、Vue2/Vue3、多端兼容、云开发。但光看词容易误解——它不是“支持Vue2和Vue3”的简单兼容,而是通过一套精巧的编译时条件注入机制,让同一份业务逻辑代码,在Vue2项目里走Options API + Vue Router 3,在Vue3项目里自动切换Composition API + Vue Router 4,连路由守卫、状态管理、组件通信都无需改一行业务代码。uniCloud也不是只当个数据库中转站,它的云函数+云数据库+云存储三位一体能力被深度耦合进了提货主流程:比如卡密校验不走客户端JS正则,而是由validate-cardcode云函数执行带防刷策略的原子校验;库存扣减不是前端算完再提交,而是由deduct-inventory云函数在事务内完成“查-锁-扣-记”四步闭环,杜绝超卖。
这套系统真正解决的是企业级落地的三个硬骨头:第一,业务一致性——H5页面扫码提货、微信小程序分享领卡、App内嵌页核销,三端操作必须共享同一套库存状态、同一套用户权限、同一套操作日志,不能出现“小程序显示有库存,H5提示已售罄”这种低级错误;第二,运维轻量化——客户IT部门只有1名兼职运维,没法搭Redis集群、配Nginx负载、管MySQL主从,所以所有服务都跑在uniCloud上,数据库自动备份、函数冷启动优化、CDN静态资源托管全由平台兜底;第三,二次开发成本可控——新增“企业批量发卡”功能时,我们只改了3个文件:/pages/admin/batch-issue.vue(新页面)、/cloudfunctions/batch-issue/index.js(新云函数)、/components/uni-data-picker-card-batch.vue(复用picker组件),没碰任何底层框架代码。这背后是69个JS工具脚本和56个Vue组件形成的“能力基座”,不是堆砌出来的代码量,而是可组合、可替换、可追溯的工程化资产。
如果你正在评估是否要接手一个礼品卡系统开发项目,或者手头已有老系统想升级,别急着写需求文档——先本地跑通这套源码,用它自带的测试卡密(TEST2024001)走一遍提货全流程,感受下从扫码识别、卡密输入、实名认证、地址选择、库存锁定到最终生成提货单的丝滑度。你会发现,很多你以为需要花两周攻坚的环节,其实已经被封装成一行调用:await this.$clientdb('gift_card').where({code: this.cardCode}).get() 就能安全查卡,this.$validate.form(this.ruleSet, this.formData) 就能触发全字段校验,<uni-data-picker v-model="selectedProvince" collection="opendb-province-city-district" field="name" /> 就能拉出三级联动地址库。这不是偷懒,而是把重复造轮子的时间,省下来专注解决真正的业务差异点——比如你们家的礼品卡要绑定手机号才能提货,或者要按区域限制提货网点,这些才是该你写的代码。
2. 架构设计:为什么选Vue2/Vue3双兼容+uniCloud,而不是Vue3+Node.js?
2.1 双版本兼容不是妥协,而是面向真实团队的务实选择
很多技术人看到“Vue2/Vue3双兼容”第一反应是:“这架构过时了”“强行兼容增加复杂度”。但现实是:我服务过的17家中小型企业客户里,有12家的前端团队仍维护着Vue2的老项目(电商后台、CRM系统、内部OA),他们不可能为了一个礼品卡模块就推翻整个技术栈。如果强制要求Vue3,意味着要么让老项目团队学新语法,要么让新项目团队维护两套代码,人力成本翻倍。这套源码的解法很朴素:用vue-compat作为Vue2项目的渐进式升级桥接层,用@vue/composition-api作为Vue3项目的向后兼容插件,核心业务逻辑全部抽离到/utils和/services目录下,通过import { useCardService } from '@/services/card'统一调用,完全屏蔽框架差异。
具体怎么实现?关键在main.js的入口配置:
// main.js
import Vue from 'vue'
import App from './App.vue'
// 根据package.json中的vue版本动态加载兼容层
const isVue3 = process.env.VUE_VERSION === '3'
if (isVue3) {
// Vue3项目:使用createApp创建实例
import { createApp } from 'vue'
const app = createApp(App)
app.mount('#app')
} else {
// Vue2项目:保持new Vue()方式
new Vue({
render: h => h(App)
}).$mount('#app')
}
更巧妙的是组件层面的处理。比如/components/uni-card-input.vue这个卡密输入框,它同时支持Vue2的props定义和Vue3的defineProps语法:
<!-- uni-card-input.vue -->
<script>
export default {
name: 'UniCardInput',
props: {
value: String,
placeholder: String
},
setup(props, { emit }) {
// Vue3 Composition API写法
const handleChange = (e) => {
emit('input', e.target.value)
emit('update:value', e.target.value)
}
return { handleChange }
},
// Vue2 Options API回退方案
methods: {
__handleChange(e) {
this.$emit('input', e.target.value)
this.$emit('update:value', e.target.value)
}
}
}
</script>
编译时,Vue CLI会根据目标版本自动剔除无用代码。Vue2构建产物里看不到setup函数,Vue3构建产物里不会打包methods里的__handleChange。这不是“写两套”,而是用一套代码覆盖两种运行时——就像同一块电路板,插在不同型号的主机里自动适配供电电压。
2.2 uniCloud替代传统后端,不是为省钱,而是为降风险
有人问:“为什么不自己搭Node.js服务?”答案很现实:运维风险远大于开发风险。去年有个客户坚持自建后端,结果上线三天就遭遇两次DDoS攻击,对方不是冲业务来的,专扫暴露在公网的Node.js端口,导致整个礼品卡系统瘫痪。而uniCloud的云函数默认不暴露公网IP,所有请求必须经由uni-app的SDK签名验证,天然具备防刷能力。更重要的是,uniCloud的数据库操作是声明式的,不是SQL语句:
// clientdb.js 封装的云数据库操作
export const db = uniCloud.database()
export const giftCardCollection = db.collection('gift_card')
// 安全的查询:自动过滤敏感字段,自动添加租户ID隔离
export const getCardByCode = async (code) => {
return await giftCardCollection.where({
code: code,
status: 'active', // 状态过滤
expire_time: { $gt: Date.now() } // 时间过滤
}).field('code, title, amount, stock') // 字段白名单
.get()
}
// 原子性扣减:云函数内执行,避免竞态
export const deductStock = async (cardId) => {
const res = await uniCloud.callFunction({
name: 'deduct-inventory',
data: { card_id: cardId }
})
return res.result
}
这段代码背后是uniCloud的三大保障:第一,field()方法强制字段白名单,即使数据库里存了secret_key字段,只要没写进field参数,永远查不出来;第二,where条件里的$gt是服务端执行,客户端传来的Date.now()时间戳会被云函数自动校验,防止篡改;第三,deduct-inventory云函数内部用的是MongoDB的findAndModify原子操作,不是先查再改,彻底规避超卖。这些能力如果自己用Node.js实现,至少要额外引入JWT鉴权、MongoDB事务、RateLimit限流中间件,开发周期延长40%,且每个环节都是潜在故障点。
2.3 多端兼容的本质,是UI层与逻辑层的彻底解耦
很多人以为“多端兼容”就是写一套代码编译成不同平台,但实际难点在于平台能力差异。比如微信小程序的wx.chooseAddress和App的uni.chooseAddress返回格式不同,H5的扫码API要用uni.scanCode但需手动处理iOS/Android兼容。这套源码的解法是:所有平台特有API都封装进/platform目录,业务层只调用统一接口:
// /platform/address.js
export const chooseAddress = () => {
return new Promise((resolve, reject) => {
if (process.env.UNI_PLATFORM === 'mp-weixin') {
// 微信小程序专用逻辑
wx.chooseAddress({
success: (res) => resolve({
name: res.userName,
phone: res.telNumber,
address: `${res.province}${res.city}${res.county}${res.detailInfo}`
})
})
} else if (process.env.UNI_PLATFORM === 'app') {
// App专用逻辑
uni.chooseAddress({
success: (res) => resolve(res)
})
} else {
// H5降级方案:弹表单手动填写
resolve({
name: prompt('请输入收货人'),
phone: prompt('请输入手机号'),
address: prompt('请输入详细地址')
})
}
})
}
// 业务组件中调用
import { chooseAddress } from '@/platform/address'
export default {
methods: {
async handleSelectAddress() {
try {
const address = await chooseAddress()
this.formData.address = address.address
} catch (e) {
uni.showToast({ title: '获取地址失败', icon: 'none' })
}
}
}
}
这种设计让业务逻辑彻底脱离平台细节。当你需要新增抖音小程序支持时,只需在/platform/address.js里补一段mp-toutiao分支代码,所有调用chooseAddress()的地方自动生效,不用改任何业务组件。这才是真正的“一次开发,多端运行”,而不是“一次开发,到处调试”。
3. 核心模块拆解:69个JS工具、56个组件、13个SCSS如何协同工作
3.1 69个JS工具脚本:不是代码堆砌,而是分层能力矩阵
这69个JS文件绝非随意罗列,而是按职责划分为五个能力层,每一层都解决一类共性问题:
第一层:基础支撑(12个)
utils/request.js 封装uni.request,自动添加token、错误重试、超时控制;
utils/storage.js 统一管理localStorage/sessionStorage,支持加密存储敏感信息;
utils/date.js 提供ISO8601格式化、相对时间计算(如“2小时前”)、节假日判断;
utils/number.js 解决JavaScript浮点数精度问题,formatMoney(199.999) 输出200.00;
utils/string.js 包含中文字符截断、手机号掩码(138****1234)、URL参数解析。
第二层:业务抽象(23个)
services/card.js 卡密校验、库存查询、提货记录生成;
services/user.js 用户实名认证、收货地址管理、提货历史查询;
services/order.js 提货单创建、物流信息同步、电子凭证生成;
services/feedback.js 封装opendb-feedback,支持图片上传、分类标签、工单流转;
services/auth.js 权限校验,区分普通用户、门店管理员、总部运营三类角色。
第三层:UI交互(18个)
directives/click-outside.js 点击外部关闭下拉菜单;
mixins/form-mixin.js 表单通用逻辑:重置、校验、提交状态管理;
filters/time-filter.js 全局时间过滤器,{{ timestamp | formatDate }};
plugins/vant-loader.js 按需加载Vant组件,减少首屏体积;
components/uni-data-picker.js 数据联动选择器核心逻辑,支持无限级联。
第四层:平台适配(9个)
platform/wechat.js 微信JSSDK配置、分享接口、支付回调处理;
platform/alipay.js 支付宝小程序API适配;
platform/app.js App原生能力调用(蓝牙、NFC、定位);
platform/h5.js H5端微信授权、浏览器兼容性处理;
platform/clipboard.js 跨平台剪贴板操作封装。
第五层:工程增强(7个)
build/webpack.config.js Vue2/Vue3双构建配置;
build/unpackage.js 自动解包云函数到/cloudfunctions目录;
build/iconfont.js 自动生成图标字体CSS和icons.js映射;
build/changelog.js 从Git提交记录生成changelog.md;
build/license-checker.js 扫描依赖许可证合规性。
提示:所有工具脚本都遵循“单一职责+零依赖”原则。比如
validate.js只做校验规则定义,不耦合UI组件;clientdb.js只封装数据库操作,不处理业务逻辑。这样当你需要替换表单验证库时,只需重写validate.js,其他58个文件完全不受影响。
3.2 56个Vue组件:可组合、可替换、可追溯的设计哲学
这56个组件不是“拿来即用”的黑盒,而是按复用粒度分为三级:
原子组件(21个)
最小功能单元,无业务逻辑,纯UI呈现:
uni-icon(图标)、uni-button(按钮)、uni-input(输入框)、uni-swipe-action(滑动操作)、uni-rate(评分)。它们的特点是:Props精简(通常≤5个)、事件语义化(@click、@change)、样式完全CSS变量控制(--uni-color-primary)。比如uni-button的尺寸通过size="large"控制,而非写死height: 44px,方便全局主题切换。
分子组件(23个)
组合原子组件,封装特定场景交互:
uni-card-input(卡密输入框,带自动聚焦、粘贴解析、格式校验);
uni-address-selector(地址选择器,集成uni-data-picker,支持搜索+定位+历史记录);
uni-qr-scan(扫码组件,自动识别卡密、跳转提货页、失败重试);
uni-order-summary(订单摘要,聚合商品、运费、优惠、实付金额);
uni-feedback-form(反馈表单,带图片上传、分类选择、进度条)。
业务组件(12个)
直接对应业务流程,包含完整业务逻辑:
page-card-scan.vue(扫码提货页,协调扫码、卡密解析、跳转逻辑);
page-card-validate.vue(卡密校验页,调用云函数、展示卡信息、引导实名);
page-card-confirm.vue(确认提货页,地址选择、数量选择、协议勾选);
page-card-success.vue(提货成功页,电子凭证生成、分享按钮、物流查询);
page-admin-stock.vue(库存管理页,表格渲染、批量导入、预警设置)。
注意:所有组件都遵循“props驱动+事件通知”模式。比如
uni-card-input接收value和placeholder,通过@input和@blur抛出事件,绝不直接修改父组件data。这保证了组件可预测性——你永远知道它什么时候触发什么事件,不会出现“点了按钮没反应,查半天发现是组件内部this.$emit写错了”。
3.3 13个SCSS样式文件:基于BEM的模块化CSS体系
这13个SCSS文件不是简单地把CSS拆成多个文件,而是构建了一套可扩展的样式架构:
基础层(4个)
_variables.scss:定义所有CSS变量,包括颜色($primary-color)、间距($spacing-xs)、圆角($radius-sm)、字体($font-size-base);
_mixins.scss:封装常用Mixin,如@mixin flex-center(居中布局)、@mixin ellipsis(文本省略)、@mixin responsive(响应式断点);
_reset.scss:重置默认样式,移除ul/ol默认padding、button默认边框、a标签下划线;
_base.scss:基础样式,如body字体设置、全局链接颜色、禁用用户选择。
组件层(7个)
每个原子组件对应一个SCSS文件:_button.scss、_input.scss、_icon.scss等。采用BEM命名规范:
.uni-button {
&--primary { background-color: $primary-color; }
&--disabled { opacity: 0.6; }
&__text { font-weight: 600; }
}
这样.uni-button--primary和.uni-button__text永远不会冲突,即使项目引入第三方UI库也能和平共处。
主题层(2个)
_theme-light.scss(浅色主题)、_theme-dark.scss(深色主题),通过CSS变量切换:
:root {
--uni-color-primary: #409eff;
--uni-bg-color: #ffffff;
}
[data-theme="dark"] {
--uni-color-primary: #66b1ff;
--uni-bg-color: #1f1f1f;
}
只需在HTML根节点加data-theme="dark",所有组件自动变色,无需重写任何样式。
实操心得:我在给客户做主题定制时,发现直接改
_variables.scss里的$primary-color值,然后重新编译,整个系统的主色调就变了。但要注意:图标颜色不能直接用color: $primary-color,因为图标是PNG或字体,得用filter: hue-rotate()或CSS变量控制字体图标颜色。这点在_icon.scss里有专门处理。
4. 实操部署:从本地调试到三端上线的完整链路
4.1 本地开发环境搭建(5分钟极速启动)
第一步:确认Node.js版本≥14.18(uni-app官方要求),推荐使用nvm管理多版本:
# macOS/Linux
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash
source ~/.bashrc
nvm install 16.20.2
nvm use 16.20.2
第二步:安装HBuilderX(官方IDE,对uni-app支持最完善)或VS Code(需安装uni-app插件)。我推荐HBuilderX,因为它的云函数调试、真机运行、一键打包功能比VS Code更成熟。
第三步:克隆源码并安装依赖:
git clone https://github.com/your-repo/gift-card-system.git
cd gift-card-system
npm install
# 如果是Vue2项目,还需额外安装兼容包
npm install vue-compat@3.2.47 --save-dev
第四步:启动本地服务:
# 启动H5端(默认端口8080)
npm run serve:h5
# 启动微信小程序(需提前安装微信开发者工具)
npm run serve:mp-weixin
# 启动App端(需连接真机或模拟器)
npm run serve:app
注意:
npm run serve:*命令由vue.config.js中的configureWebpack配置驱动,它会根据UNI_PLATFORM环境变量自动注入对应平台的SDK。比如serve:mp-weixin会注入微信JSSDK,serve:app会注入uni-app的App SDK。不要手动修改main.js去适配平台,那是反模式。
4.2 uniCloud云端环境配置(3步完成)
uniCloud提供阿里云和腾讯云两个服务商,这里以阿里云为例(腾讯云步骤类似):
第一步:开通uniCloud服务
登录DCloud开发者中心 → 进入uniCloud控制台 → 创建新服务空间 → 选择“阿里云” → 设置空间名称(如gift-card-prod)→ 完成开通。系统会自动生成service space ID(如service-xxxxxx),记下来备用。
第二步:关联本地项目
在HBuilderX中右键项目根目录 → “uniCloud” → “关联云空间” → 输入service space ID → 点击确定。此时HBuilderX会自动在项目根目录生成.env文件,内容类似:
UNI_CLOUD_SERVICE_SPACE_ID=service-xxxxxx
UNI_CLOUD_PROVIDER=aliyun
第三步:上传云函数与数据库
右键/cloudfunctions目录 → “上传所有云函数” → 等待上传完成。接着右键/uniCloud/database目录(如果有)→ “初始化云数据库”。此时uniCloud会自动创建gift_card、user_address、order_log等集合,并应用schema.json中的数据表结构和权限规则。
关键细节:云函数上传后,HBuilderX会在控制台显示每个函数的访问URL,形如
https://gift-card-prod.service.xxxx.aliyuncs.com/deduct-inventory。但业务代码中绝不直接调用这个URL,而是用uniCloud.callFunction({name: 'deduct-inventory'}),因为uni-app SDK会自动处理签名、鉴权、错误重试。硬编码URL会导致跨平台失效(App端无法访问HTTPS URL)。
4.3 三端差异化配置与打包
H5端配置要点
修改manifest.json中的h5节点:
"h5": {
"title": "XX礼品卡提货中心",
"template": "index.html",
"domain": "https://giftcard.yourcompany.com",
"router": {
"base": "/",
"mode": "history"
},
"sdkConfigs": {
"map": {
"provider": "amap", // 高德地图
"key": "your-amap-key"
}
}
}
打包命令:npm run build:h5,产物在dist/build/h5目录,可直接部署到Nginx或CDN。
微信小程序配置要点
修改manifest.json中的mp-weixin节点:
"mp-weixin": {
"appid": "wx1234567890abcdef",
"setting": {
"urlCheck": false
},
"usingComponents": true,
"permission": {
"scope.userLocation": {
"desc": "用于获取您的位置,推荐附近提货点"
}
}
}
特别注意:微信要求appid必须与公众号/小程序后台一致,否则无法调起支付。打包前务必在微信开放平台绑定业务域名(giftcard.yourcompany.com)。
App端配置要点
修改manifest.json中的app-plus节点:
"app-plus": {
"usingFeatures": ["NSPhotoLibraryUsageDescription", "NSCameraUsageDescription"],
"nvueStyleCompiler": "uni-app",
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
}
}
打包需在HBuilderX中:发行 → 原生App云打包 → 选择“自定义基座”(推荐,避免公共基座兼容性问题)→ 上传证书(iOS需p12证书,Android需jks证书)→ 提交打包。iOS包需上传至TestFlight,Android包可直接分发APK。
实操心得:我踩过最大的坑是App端的HTTPS证书。uni-app默认启用SSL校验,如果后端API域名证书不是由权威CA签发(比如用Let’s Encrypt免费证书),App会报“net::ERR_CERT_DATE_INVALID”。解决方案是在
/platform/app.js里添加信任:
// App端忽略证书校验(仅限测试环境!)
if (process.env.NODE_ENV === 'development') {
uni.setStorageSync('ignoreSSL', true)
}
4.4 生产环境部署 checklist
| 项目 | 检查项 | 是否完成 | 备注 |
|---|---|---|---|
| 前端资源 | H5静态资源已上传CDN,设置缓存策略(HTML 1小时,JS/CSS 1年) | ☐ | CDN需配置跨域头Access-Control-Allow-Origin: * |
| 微信小程序 | appid与小程序后台一致,业务域名已备案并添加到“request合法域名” | ☐ | 域名必须带https://,不能是IP |
| App端 | iOS证书有效期≥1年,Android签名与发布版一致 | ☐ | 签名不一致会导致更新失败 |
| uniCloud | 云函数并发配额≥100,数据库读写CU足够(建议500CU起步) | ☐ | 并发不足会导致高峰期请求排队 |
| 安全加固 | 所有云函数开启“仅允许uni-app调用”,数据库集合权限设为“仅云函数可读写” | ☐ | 防止恶意请求绕过前端直接调用 |
| 监控告警 | 在uniCloud控制台配置“云函数错误率>1%”告警,发送至企业微信 | ☐ | 及时发现卡密校验失败等异常 |
5. 常见问题与排查技巧实录
5.1 卡密校验失败:90%的问题出在“时间校验”上
现象:用户输入正确卡密,但提示“卡已过期”或“卡未生效”。
排查路径:
1. 查看云函数日志:HBuilderX → uniCloud → 选择空间 → validate-cardcode函数 → 查看最近调用日志;
2. 检查日志中的expire_time和start_time字段,对比当前服务器时间(Date.now());
3. 发现expire_time是1672531200000(2023-01-01),但当前时间是1712531200000(2024-04-08),明显过期。
根本原因:卡密生成时用了客户端时间(new Date().getTime()),而客户端时间可能被用户手动修改。uniCloud云函数的时间是服务端时间,绝对可靠。
解决方案:所有时间相关字段(start_time、expire_time、used_time)必须由云函数生成,前端只传业务参数(如“有效期30天”),云函数计算绝对时间戳:
// 云函数中
exports.main = async (event, context) => {
const { validDays } = event // 前端传“30”
const startTime = Date.now()
const expireTime = startTime + validDays * 24 * 60 * 60 * 1000
await db.collection('gift_card').add({
start_time: startTime,
expire_time: expireTime,
// ...其他字段
})
}
注意:
Date.now()在云函数中返回的是阿里云服务器时间,精确到毫秒,且全球统一。不要用new Date().toISOString(),它包含时区信息,可能导致跨时区用户看到不同时间。
5.2 小程序扫码白屏:Canvas渲染兼容性问题
现象:微信小程序扫码页打开后一片空白,控制台报错Cannot read property 'getContext' of null。
原因分析:微信基础库2.25.0+版本对Canvas API做了安全限制,uni.createCanvasContext必须在页面onReady生命周期后调用,且Canvas元素必须已挂载到DOM。
修复方案:
1. 在page-card-scan.vue中,将Canvas初始化逻辑移到onReady钩子:
<script>
export default {
onReady() {
// 确保DOM已渲染
this.$nextTick(() => {
this.ctx = uni.createCanvasContext('qrcode-canvas', this)
this.drawQrCode()
})
}
}
</script>
- 检查
<canvas>标签是否设置了id="qrcode-canvas"且没有v-if指令(v-if会导致DOM未创建); - 在
manifest.json中升级基础库版本:
"mp-weixin": {
"minPlatformVersion": "2.25.0"
}
5.3 App端定位失败:权限配置遗漏
现象:App点击“获取当前位置”无反应,或提示“定位服务未开启”。
排查清单:
- ✅ Android:AndroidManifest.xml中已添加<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>;
- ✅ iOS:Info.plist中已添加NSLocationWhenInUseUsageDescription键,值为“用于推荐附近提货点”;
- ✅ App端代码中调用uni.getLocation前,已用uni.authorize申请权限:
async getLocation() {
try {
await uni.authorize({ scope: 'scope.userLocation' })
const res = await uni.getLocation({ type: 'gcj02' })
this.position = res
} catch (e) {
if (e.errMsg.includes('authorize:fail')) {
// 引导用户去设置页开启权限
uni.openSetting()
}
}
}
- ✅ 真机测试:模拟器无法获取真实定位,必须用真机测试。
5.4 云数据库查询为空:集合名称拼写错误
现象:db.collection('gift_card').get()返回空数组,但控制台确认集合里有数据。
终极排查法:
1. 登录uniCloud控制台 → 数据库 → 查看集合列表,确认集合名确实是gift_card(不是giftCard或gift-card);
2. 在云函数中打印集合引用:
exports.main = async (event, context) => {
console.log('collection ref:', db.collection('gift_card'))
// 输出类似:CollectionRef { collectionName: "gift_card", ... }
}
- 如果输出
collectionName为空,说明集合名传错了; - 如果输出正确,检查集合权限:点击集合 → “权限设置” → 确认“读取”权限勾选了“云函数”。
经验技巧:所有数据库集合名、云函数名、组件名,统一用小写字母+下划线命名(
gift_card,deduct_inventory,uni_card_input),避免大小写混淆。uni-app对大小写敏感,GiftCard和giftcard是两个不同集合。
5.5 表单校验不触发:validate.js规则未注册
现象:点击提交按钮,this.$validate.form()返回true,但实际字段未校验。
原因:validate.js中的规则集未在Vue原型上注册。
检查main.js是否包含:
import { validate } from '@/utils/validate'
Vue.prototype.$validate = validate
Vue3项目则需在main.js中:
import { createApp } from 'vue'
import App from './App.vue'
import { validate } from '@/utils/validate'
const app = createApp(App)
app.config.globalProperties.$validate = validate
更推荐的方式是使用Provide/Inject,在main.js中:
app.provide('validate', validate)
然后在组件中:
import { inject } from 'vue'
const validate = inject('validate')
6. 二次开发实战:如何快速接入企业微信审批流
客户提出新需求:“提货申请需经部门主管审批,审批通过后才释放库存”。这不需要重写整个系统,只需在现有架构上叠加一层。
第一步:扩展数据库集合
在uniCloud控制台新建集合approval_flow,结构如下:
{
"order_id": "ORD20240001",
"user_id": "U123456",
"approver_id": "U678901", // 主管ID
"status": "pending", // pending/approved/rejected
"created_at": 1712531200000,
"updated_at": 1712531200000
}
第二步:新增云函数
创建/cloudfunctions/approve-order/index.js:
exports.main = async (event, context) => {
const { order_id, status, comment } = event
const db = uniCloud.database()
// 更新审批状态
await db.collection('approval_flow').where({ order_id }).update({
status,
comment,
updated_at: Date.now()
})
// 如果审批通过,执行库存扣减
if (status === 'approved') {
const order = await db.collection('order_log').where({ _id: order_id }).get()
await uniCloud.callFunction({
name: 'deduct-inventory',
data: { card_id: order.result.data[0].card_id }
})
}
}
第三步:改造提货流程
修改page-card-confirm.vue的提交逻辑:
async handleSubmit() {
// 1. 原有提货单创建逻辑
const orderId = await this.createOrder()
// 2. 新增审批流程触发
await uniCloud.callFunction({
name: 'create-approval',
data: { order_id: orderId, approver_id: this.currentUser.approver_id }
})
// 3. 页面跳转至审批状态页
uni.navigateTo({ url: `/pages/status/approval?order_id=${orderId}` })
}
第四步:新增状态页
创建/pages/status/approval.vue,用uni-data-pick实时监听审批状态:
<template>
<view class="status-page">
<text>审批状态:{{ statusText }}</text>
<uni-data-pick
collection="approval_flow"
field="status"
:where="{ order_id: orderId }"
@change="onStatusChange"
/>
</view>
</template>
<script>
export default {
data() {
return {
orderId: '',
statusText: '审批中...'
}
},
onLoad(options) {
this.orderId = options.order_id
},
methods: {
onStatusChange(e) {
this.statusText = e.value === 'approved' ? '审批通过,正在释放库存...' :
e.value === 'rejected' ? '审批拒绝,请联系主管' : '审批中...'
if (e.value === 'approved') {
setTimeout(() => {
uni.switchTab({ url: '/pages/order/success' })
}, 2000)
}
}
}
}
</script>
整个过程只新增了2个云函数、1个页面、3行业务代码,原有69个工具脚本、56个组件、13个SCSS全部复用。这就是模块化架构的价值——新需求不是“重写”,而是“组装”。
简介:开箱即用的礼品卡提货系统,支持H5、微信小程序、App三端运行,基于Vue 2与Vue 3双版本兼容架构,后端依托uniCloud云开发平台。前端包含69个核心JS工具脚本(含表单验证validate.js、云数据库操作clientdb.js、用户反馈opendb-feedback.js等),56个可复用Vue组件,13个SCSS样式文件及配套图标资源(PNG、字体图标、icons.js)。已集成uniCloud数据库增删改查、数据联动选择器(uni-data-picker.js)、权限控制基础结构、用户反馈模块和完整表单校验逻辑。配置文件齐全(pages.、manifest.、vue.config.js等),附带changelog.md变更日志与license.md开源协议说明。适用于企业礼品卡发放、核销、库存查询与用户自助提货等真实业务流程,支持一键部署、本地调试及二次开发定制。
&spm=1001.2101.3001.5002&articleId=163093818&d=1&t=3&u=be3ef3a76f6b4a55b7115b6b547a393a)
1716

被折叠的 条评论
为什么被折叠?



