






# 下载地址示例(请访问官方最新链接)
https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
你可曾碰到过领取了优惠券然而却没办法使用的那种令人尴尬的状况,又或者是小程序卡顿许久都加载不出来的情形?今天咱们就以通俗易懂的话语来谈一谈,怎样从毫无基础开始,将一个值得信赖的微信小程序优惠券系统给搭建出来,从而让你少遭遇几个困扰。
// 示例代码,创建项目时的配置参数(实际操作中由开发者工具提供)
{
"projectname": "testproject",
"appid": "your_appid",
"projectPath": "C:/your_project_path"
}
Hello, World!
{{item}}
于小程序开发工具之中,首要步骤便是填好项目名称以及 AppID。假设你计划制作一个“XX 优选优惠券”,那么就在项目名之处径直进行输入,AppID 前往微信公众平台复制便可。而后端服务需选择“不使用云服务”,如此便于我们自身把控代码。
{{message}}
// 示例JavaScript代码
Page({
data: {
message: 'Hello from JavaScript!'
}
});
/* 示例WXSS代码 */
.view {
width: 750rpx;
margin: 0 auto;
}
那版本号也是具备一定重要性的,最初的版本被设定为1.0.0。在每一次做出修改之后,能够依据实际需求实行升级,就像修复了一个较为严重的漏洞时便将其改成1.0.1。可不要小瞧了这个数字,它能够助力你去分辨正在线上运行的究竟是哪一个版本,一旦出现问题便能够迅速地进行回滚操作。
/* 示例WXSS代码 */
@import url("another.wxss");
/* 示例WXSS代码 */
.flex-container {
display: flex;
flex-direction: row;
justify-content: space-around;
}
建议选择基础库版本为最新的稳定版,像是3.0.0以上。这会决定你能够使用哪些新的API,因为老版本或许会不支持某些功能。要是你想要兼容低版本微信,那么可以将最低基础库版本设置为2.20.0,并且通过wx.getSystemInfo获取用户当前版本来做提示。
/* 示例WXSS代码 */
page {
background-color: #f8f8f8;
}
/* 示例WXSS代码 */
/* 避免使用过于复杂的后代选择器 */
/* .container .header .nav .item */
/* 直接使用类选择器更高效 */
.nav-item {
/* styles */
}
有那么一种东西,它就好似那盖房子所用的砖头一般,其职责在于将各类事物放置到恰当的位置之上,这种东西便是WXML ,举例来说,要是你打算呈现一张满100减20的券,那就得用view标签进行包裹,在其内部再放置image用以展现券图, text则用于书写金额以及条件 ,数据绑定借助双大括号来达成,就如同{{coupon.name}}这样。
/* 示例WXSS代码 */
@media screen and (max-width: 400px) {
.view {
padding: 10px;
}
}
WXSS乃是针对房子开展刷漆以及贴砖操作,以此去管控颜色、管控大小、还有管控间距,就好比设定优惠券卡片的背景呈现为白色,设定其圆角为12px,设定其阴影为浅灰色。组件样式必须要进行封装妥善,不能让一个页面的样式跑到另一个页面当中去,进而造成混乱情况。
let count = 0; // 数值类型
count = "I am a string now"; // 字符串类型
function add(a, b) {
return a + b; // 返回两数之和
}
// 调用函数
let result = add(3, 4);
console.log(result); // 输出:7
将布局主要依托于flex来进行构建。其对于父容器而言,需设置display为flex,之后再去对flex-direction进行调整,以此来确定是横向还是纵向的布局方式。比如说,当需实现一排显示两张券的布局效果时,那就得给父容器设定flex-wrap为wrap,并且将每个子项的宽度设置为50%。通过这样的操作,能够确保在不同屏幕环境下券都可以实现整齐排列。
// 监听页面滚动事件
wx.onPageScroll((res) => {
console.log('页面滚动位置', res.scrollTop);
});
const myPromise = new Promise((resolve, reject) => {
// 异步操作完成后,调用resolve或reject
if (/* 成功条件 */) {
resolve('成功结果');
} else {
reject('失败原因');
}
});
myPromise.then((result) => {
console.log(result); // 成功操作的回调
}).catch((error) => {
console.log(error); // 失败操作的回调
});
Page({
onLoad: function(options) {
// 页面加载时执行
},
onReady: function() {
// 页面初次渲染完成时执行
},
onShow: function() {
// 页面显示时执行
},
onHide: function() {
// 页面隐藏时执行
},
onUnload: function() {
// 页面卸载时执行
}
});
// 在Page中定义setData
Page({
data: {
userInfo: {}
},
onLoad: function(options) {
// 获取应用实例
const app = getApp();
// 设置全局状态
app.setData({
userInfo: {
nickName: 'Zhang San',
avatarUrl: 'http://.../avatar'
}
});
// 使用全局状态
this.setData({
userInfo: app.globalData.userInfo
});
}
});
当小程序页面从开启直至关闭之时,将会历经onLoad、onShow、onReady、onHide等不同阶段。其中,onLoad这种情况适宜用于开展初始数据的请求操作,就像加载优惠券列表这一行为。而onShow每次当页面呈现出来之际均会被触发,它比较适合用于刷新数据,例如在领取优惠券之后返回查看余额这种情形。
Page({
onInput: function(e) {
console.log(e.detail.value); // 当输入框内容改变时执行
},
onTap: function() {
console.log('按钮被点击'); // 当按钮被点击时执行
}
});
在进行逻辑撰写期间,函数被用于对用户点击领券按钮这一行为予以处理,借助bindTapGetCoupon示例函数,首先要对用户登录状态展开检查,接着才会调用后端接口,所谓事件驱动,指的是用户点击一次,我们便执行一段对应的代码,其本质相当简单。
{{ message }}
Page({
data: {
message: 'Hello, World!'
},
updateMessage: function() {
// 更新message值,页面会自动更新
this.setData({
message: 'Hello, 小程序!'
});
}
});
进行数据更新时得运用this.setData,可千万别直接去修改this.data。举例来说,当领券成功之后,要将页面之上的“立即领取”转变为“已领取”,那就执行setData({ status: ‘claimed’ })。如此这般,界面便会自动实现刷新,并不需要手动对DOM进行操作。
// 示例代码
Page({
onLoad: function (options) {
// 页面加载时获取数据
this.fetchData();
},
onShow: function () {
// 页面显示时的逻辑处理
},
onReady: function () {
// 页面初次渲染完成后的逻辑处理
},
onHide: function () {
// 页面隐藏时的逻辑处理
},
onUnload: function () {
// 页面卸载时的资源清理
}
});
// 管理用户会话状态示例
Page({
data: {
isLoggedIn: false
},
onLoad: function() {
this.checkSession();
},
onShow: function() {
if (this.data.isLoggedIn) {
// 显示用户信息
}
},
onHide: function() {
// 隐藏用户信息
},
checkSession: function() {
// 根据会话状态更新isLoggedIn
}
});
点开优惠券页面的速度迟缓,用户极有可能径直划走。优化的策略存在若干:其一,将关键数据借助wx.setStorageSync存储至本地,像是用户已经领取优惠券的ID清单。其二,图片采用webp格式,并且设定懒加载,仅仅加载于屏幕范围内能够看见的券图。
// 异步加载数据示例
Page({
data: {
userInfo: null
},
onLoad: function () {
wx.request({
url: 'https://example.com/user/info',
success: res => {
this.setData({
userInfo: res.data
});
}
});
}
});
{{item.name}}
当网络状况欠佳时,首先揭示缓存数据,于onLoad之中借助wx.getStorageSync去读取上次的券列表,随后迅速进行渲染呈现,接着再次发出请求抓取最新数据,于此之后加以比较进而实施更新,如此这般用户便勿会遇上白屏状况,体验也会好出许久几分。
// 使用本地存储缓存数据示例
Page({
onLoad: function () {
const data = wx.getStorageSync('myData');
if (data) {
this.setData({ data });
} else {
// 从服务器获取数据,并存储到本地
wx.request({
url: 'https://example.com/data',
success: res => {
wx.setStorageSync('myData', res.data);
this.setData({ data: res.data });
}
});
}
}
});
// 数据缓存策略示例
Page({
data: {
cachedData: null
},
onLoad: function () {
this.getDataFromStorage();
},
watch: {
'someData': function(newVal, oldVal) {
if (newVal !== oldVal) {
// 数据变化,可能需要更新UI或重新获取数据
}
}
},
getDataFromStorage: function() {
const data = wx.getStorageSync('someData');
if (data) {
this.setData({ cachedData: data });
} else {
// 从服务器获取数据
wx.request({
url: 'https://example.com/someData',
success: res => {
wx.setStorageSync('someData', res.data);
this.setData({ cachedData: res.data });
}
});
}
}
});
本地存储切不可不管三七二十一啥都一股脑地往里面塞,对于每个券的详细信息以及过期时间这类变化幅度较大的数据来说,存储5分钟时长其实就已然足够了,而优惠券规则以及使用说明等这种属于静态性质的数据呢,则能够存储24小时,通过合理地去设置过期策略,如此是可以减少服务器压力的。
# Python伪代码示例
def apply_discount(order, coupon):
if coupon.type == 'discount':
discount_rate = coupon.value
order.total -= order.total * discount_rate
elif coupon.type == 'cash':
discount_amount = coupon.value
order.total -= discount_amount
if order.total < 0:
order.total = 0
return order
def check_coupon_limitations(user, coupon, order):
if not check_date(coupon):
return "优惠券已经过期"
if not check_user_eligibility(user, coupon):
return "用户不符合使用条件"
if not check_product_eligibility(order, coupon):
return "订单中的商品不符合优惠券条件"
return True
优惠券表当中,coupon_id作为主键,type用于区分满减券还是折扣券,denomination指代减多少钱或者打几折,condition是使用门槛像满100,有一个expire_time用来存储过期时间,status表示未领取、已领取、已使用,这些是几个核心字段。
对于用户表而言,除了 openid 以及昵称之外,还需要有一个 coupon_ids 字段,此字段的作用是用来存储该用户所领到的优惠券 ID 列表。要注意的是,金额字段应当使用 decimal 类型,以此来避免精度丢失。举例来说,像打 8.5 折这种情况,倘若用 float 类型计算,可能会得出 85.0000001 的结果,而采用 decimal 类型则能够精确到 85.00。
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password_hash` varchar(255) NOT NULL,
`email` varchar(255),
`phone` varchar(20),
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `coupon` (
`id` int NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`description` text,
`discount` decimal(5,2), -- 折扣额度
`type` enum('discount','min_discount') NOT NULL, -- 优惠类型
`value` decimal(10,2), -- 优惠券面值
`quantity` int NOT NULL, -- 发放数量
`issued` int NOT NULL, -- 已发放数量
`start_date` date NOT NULL,
`end_date` date NOT NULL,
`is_active` tinyint(1) NOT NULL DEFAULT 1, -- 是否激活状态
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
要使后端API设计得简洁,POST /coupon/receive用于领券,需传递用户openid以及优惠券ID作为参数,服务端首先要校验券存在库存与否,校验券是否处于有效期内,还要校验用户是否已经领过。GET /coupon/list用于返回可用券列表,能够支持分页操作,每次加载20条数据。
graph LR
A[客户端] -->|请求| B[API网关]
B -->|转发请求| C[业务逻辑层]
C -->|查询/更新数据| D[数据库]
D -->|返回数据| C
C -->|响应数据| B
B -->|响应| A
fetch('/api/user/coupon/redeem', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ coupon_id: 1 }),
})
.then(response => response.json())
.then(data => {
if(data.status === 'success'){
alert('优惠券领取成功!');
} else {
alert('领取失败,请稍后重试!');
}
})
.catch(error => {
console.error('Error:', error);
});
放在一个事务里的是用户领券与扣减库存,比如说先去查库存,发现还有10张,接着插入一条有关用户领券的记录,与此同时把库存更新成9。要是中间的任何一步出现失败,那么整个操作就要回滚,不会出现领券成功了可是库存却没减少的状况。
START TRANSACTION;
UPDATE coupon SET issued = issued + 1 WHERE id = 1;
INSERT INTO user_coupon (user_id, coupon_id) VALUES (1, 1);
COMMIT;
校验条件需明晰地书就,检查有效期,即查看当下时间是否处于start与end之间,检查用户资格,诸如针对新用户专享券,便要判定用户注册时间,检查商品范围,假使指定品类可用,那就得验证订单里的商品类型。
try:
db.session.begin()
db.session.query(Coupon).filter_by(id=coupon_id).update({"issued": Coupon.issued + 1})
db.session.query(UserCoupon).filter_by(user_id=user_id, coupon_id=coupon_id).update({"timestamp": datetime.now()})
db.session.commit()
except Exception as e:
db.session.rollback()
# 处理异常,比如记录日志等
出现错误进行处理之时,要返回清晰明确的提示,举例来说,要是库存不足这种情况,就要返回400以及“券已抢光”,要是用户已经领过,那就返回409以及“每人限领一张”,前端接收到这些后,给予一个Toast提示即可,不要直接返回500,因为用户看不懂500所代表含义,而且也不知道面对着这种返回该采取怎样的办法去应对。
再问你一回问题:你于开发优惠券系统之际,碰到过最具坑害性的错误是啥?欢迎在评论区域留言予以分享,点个赞以使更多小伙伴能够瞅见,一同避开此坑。
