简介:这是一套面向开发者与平台运营者的任务悬赏、三级分销返佣及积分商城一体化前端源码,基于Vue.js构建,可用于快速搭建用户拉新返佣平台。系统支持会员等级差异化返佣比例、任务发布与审核、放弃任务设置、联盟接口对接(8/2分成)、提现规则配置、积分商城兑换礼品等功能,后台同时提供财务统计、用户管理、任务分类与幻灯片调用等模块,适合需要快速上线或二次开发营销平台的团队。压缩包共2000个文件,包含vue组件、js交互逻辑、php后端接口、png图片素材、html页面、css样式等类型,资源包大小327.53MB,基本涵盖前后端完整项目与部署所需文件。已有693人学习下载。资源可帮助读者理解分销返佣系统的完整业务流程,掌握Vue+PHP+数据库的整合方式,并可直接参考目录结构进行功能扩展或界面改造,节省从零搭建的时间。
1. 任务悬赏平台的返佣模型,为什么比普通发单系统难做
拿到这套带 Vue 源码的任务悬赏系统时,我关心的不是页面有多丰富,而是返佣怎么做到不重复、不丢单。普通发单系统只需要维护一个任务订单状态,这套系统却在前面加了会员等级返佣、三级分销返佣、联盟任务 8/2 分配三层逻辑,任何一层算错,后台对账都会对不上。
作为后端开发,我看它的角度是:这套源码把“用户做任务得钱、邀请人抽成、赚积分兑换礼品”的拉新活动闭环做成了可视化配置项。适合想快速搭建带会员体系的营销平台的人,也适合研究返佣状态机与前端 Vue 接口设计的人。下面我会从表结构、状态机、分销链路、联盟与积分、部署验证五个方向拆开讲。
2. 任务流程与状态机:返佣不能只靠一个字段
2.1 任务、订单、资金流水三张表的分工
拆源码时,我习惯先找订单状态是在哪张表里变的。这套系统里至少有task、task_order、fund_flow三张表,分工完全不同。
| 表名 | 关键字段 | 解决什么问题 |
|---|---|---|
| task | id, category_id, task_price, stock, audit_minutes | 存任务基础信息和后台统一下发的参数 |
| task_order | id, task_id, user_id, status, submit_time, task_price_snapshot | 记录用户与任务的每一次互动 |
| fund_flow | id, user_id, order_id, amount, flow_type, create_time | 记录返佣、扣款、提现,用于对账 |
把这三张表拆开的好处是,任务被修改价格或下架时,不会影响用户已有的待审核订单。返佣计算只依赖task_order.task_price_snapshot,这个字段会在用户提交任务那一刻把任务佣金快照下来,之后后台改价不会影响旧订单。否则用户提交后平台涨了 0.5 元,旧订单按新价格结算,资金流水就会对不上。
2.2 任务状态机与后台审核时间参数
任务订单的状态流转我梳理后是:pending(已领取未做)→submitted(已提交待审核)→finished(审核通过并发放返佣),中途可能走向canceled或rejected。
后台“任务审核时间后台自定义设置”实际作用在定时任务上:当用户提交任务后超过audit_minutes分钟管理员没有审核,系统可以自动提醒或自动驳回。查询超时订单的常见做法是:
SELECT id, user_id, task_id, submit_time FROM task_order WHERE status = 'submitted' AND submit_time <= DATE_SUB(NOW(), INTERVAL :audit_minutes MINUTE) ORDER BY submit_time ASC LIMIT 100;这段 SQL 会找出所有“提交时间早于当前时间减审核分钟数”的订单。:audit_minutes是后台设置参数,比如设置为 60,就表示超过 60 分钟没有处理的订单会被拎出来。定时任务拿到这批订单后,可以再次调后台人工审核接口,也可以按“超时未审核自动通过”的开关处理。需要提醒的是,如果任务要求用户上传截图或填写链接,自动通过前最好校验提交内容非空,否则容易成为刷返佣的入口。
2.3 领取任务到生成返佣记录的代码路径
用户点击“领取任务”时,Vue 前端请求后端接口,后端逻辑大致是:
async function claimTask(userId, taskId) { const task = await getTaskById(taskId); const todayCount = await getTodayClaimCount(userId); if (todayCount >= task.daily_limit) { return { code: 403, msg: '今日领取次数已达上限' }; } await db.transaction(async (tx) => { await tx.update('task', { stock: task.stock - 1 }, { id: taskId, stock: task.stock - 1 }); await tx.insert('task_order', { user_id: userId, task_id: taskId, status: 'pending', task_price_snapshot: task.task_price }); }); return { code: 0, orderId: tx.lastInsertId }; }代码里的daily_limit来自“非会员每日领取设置”或会员等级的每日限额。后端用事务同时扣减任务库存和插入任务订单,避免并发请求下把任务库存扣成负数。任务提交后,返佣并不是立刻发,而是等管理员在后台点击“审核通过”,此时才执行返佣发放。这个设计很关键:如果用户提交后马上发返佣,再发现提交内容不合格退回,就需要手动扣回,对账会很痛苦。
2.4 返佣没到账时先看哪张表
线上遇到“任务完成了没收到钱”,大部分不是代码问题,而是状态没有流转到finished。排查顺序我一般是这样:
- 先去
task_order看这条订单的status,如果还在submitted,说明管理员没有点击审核通过。 - 再去
fund_flow查是否有order_id对应且flow_type = 'task_rebate'的记录,没有就说明返佣代码没有被执行。 - 最后看
users.balance是否增加,如果前两张表都正常但余额没变,多半是事务没提交或余额字段更新被忽略。
“完成不了任务信誉分降低”这个功能也是在订单变成rejected或canceled时触发的,扣分后要在用户中心的任务明细里展示原因。否则用户只看到信用分变少,不知道是因为哪个任务导致的,很容易误以为是系统吞分。
3. 三级分销与会员等级:递归返佣怎么避免重复计算
3.1 会员等级配置:返佣比例、任务价格、提现手续费
这套系统里会员不是摆设,而是直接影响计算结果的因子。后台“会员等级”配置通常对应member_level表,字段类似下面:
| 字段 | 示例值 | 作用 |
|---|---|---|
| level | 2 | 等级标识,前端展示用 |
| name | 银牌会员 | 显示名称 |
| task_rebate_rate | 0.15 | 该等级用户任务通过后拿到的返佣比例 |
| member_task_price | 2.5 | 该等级用户做任务时的返佣基数 |
| normal_task_price | 1.2 | 普通用户做同一任务时的返佣基数 |
| withdraw_fee_rate | 0.03 | 提现手续费百分比 |
| top_count_per_month | 3 | 每月可把任务置顶的次数 |
从接口约定看,前端 Vue 页面拿到的是一份 JSON:
{ "level": 2, "name": "银牌会员", "task_rebate_rate": 0.15, "member_task_price": 2.5, "normal_task_price": 1.2, "withdraw_fee_rate": 0.03, "top_count_per_month": 3 }这里task_rebate_rate是任务返佣比例,member_task_price是会员做任务的价格,normal_task_price是普通用户做任务的价格。举个例子,普通用户完成一个任务得到 1.2 元,银牌会员则按 2.5 元的基数计算返佣。因此task_order表里必须有task_price_snapshot,否则用户升级后去完成旧任务,会按新等级价格发放,产生财务漏洞。
“发布任务置顶次数的赠送问题”表示会员每个月能免费置顶若干条任务,实现上在任务表加一个is_top和top_expire_time字段即可。前端在任务列表排序时优先展示is_top = 1且top_expire_time未过的任务。
3.2 三级分销返佣链路:向上找三级用户
分销关系通常存在users.invite_id字段里,用户 A 邀请 B,B 的invite_id就是 A 的 ID。当 B 完成一个任务并获得返佣时,系统需要给 A、A 的上级、A 的上级的上级按比例发钱。
在 MySQL 里向上找三级,可以写三个 LEFT JOIN:
SELECT o.id AS order_id, u.id AS buyer_user_id, u1.id AS level1_user_id, u2.id AS level2_user_id, u3.id AS level3_user_id FROM task_order o JOIN users u ON o.user_id = u.id LEFT JOIN users u1 ON u.invite_id = u1.id LEFT JOIN users u2 ON u1.invite_id = u2.id LEFT JOIN users u3 ON u2.invite_id = u3.id WHERE o.id = :orderId;这条 SQL 从左到右把用户自己的邀请链一层层展开。如果某个上级不存在,对应的level_user_id就是 NULL,后续代码里跳过即可。需要注意,真实场景中用户可能通过邀请链接把invite_id设置成自己,所以要先校验u.invite_id != u.id,否则三级分销会变成自返。分销比例在后台独立配置,比如一级 10%、二级 5%、三级 2%,加起来不应超过任务返佣总额,否则平台会贴钱。
3.3 返佣入账的事务处理
返佣发放的经典错误是在循环里一条条 update 余额,没放到事务里。正确做法是把“更新用户余额、写资金流水、更新订单状态”包在同一个事务中:
await db.transaction(async (tx) => { const order = await tx.select('task_order', { id: orderId }, { forUpdate: true }); const rebateAmount = order.task_price_snapshot * order.member_rebate_rate; const upperUsers = await getUpperUsers(order.user_id, 3); for (const upper of upperUsers) { const amount = Math.round(rebateAmount * upper.rate * 100) / 100; await tx.increment('users', { balance: amount }, { id: upper.user_id }); await tx.insert('fund_flow', { user_id: upper.user_id, order_id: orderId, amount: amount, flow_type: 'rebate' }); } await tx.update('task_order', { status: 'finished', audit_time: new Date() }, { id: orderId }); });这里用forUpdate锁住订单,避免同一订单被系统重复审核;金额先乘以 100 再四舍五入,是为了规避 JavaScript 浮点数误差。如果订单是会员任务,member_rebate_rate是当前等级配置;如果是普通用户,就走默认比例。“用户做任务的价格与普通用户做任务的返佣不同”翻译成实现,就是订单表里存了不同来源的返佣基数,而不是每次都去读取最新的任务配置。
3.4 Vue 端的分销统计与路由参数
前端用户中心会有一个“我的分销”页面,展示一级、二级、三级人数以及累计返佣。这个页面不建议一次性查全量明细,而是按月分组:
async function loadRebateSummary(month) { const { data } = await api.get('/user/rebate/summary', { params: { range: month, page: 1, pageSize: 20 } }); this.upperCount = data.upperCount; this.totalRebate = data.totalRebate; this.list = data.list; }这里通过 Vue 路由进入/user/rebate页面,路由参数里带上当前月份,例如this.$router.push({ path: '/user/rebate', query: { month: '2026-01' } })。前端拿到数据后只做展示,不自己根据订单金额算返佣,因为分级逻辑在后端,前端重算一遍不仅浪费性能,还容易和后台统计对不上。如果后续要扩展按邀请时间筛选,可以在params里再加startTime和endTime,后端 SQL 在users.create_time上做范围过滤即可。
4. 联盟任务与积分商城:8/2 分配和库存扣减怎么落地
4.1 联盟任务对接:申请账户与 8/2 分配
这个版本里的“联盟配置”是一个比较吸引人的点。平台管理员只需要申请一个联盟账户,拿到app_id和app_secret,系统就能定时拉取联盟任务列表并同步成本。任务完成后,联盟下发佣金,系统按 8/2 分配,其中 80% 归平台用于用户返佣和平台收益,20% 作为联盟平台的服务费。
联盟任务与普通任务的区别在于,它没有平台管理员手动发布,而是通过接口同步。配置表里通常有:
| 字段 | 示例值 | 说明 |
|---|---|---|
| app_id | ali_001 | 联盟分配的账户 ID |
| app_secret | xxxx | 签名密钥 |
| rebate_rate | 0.8 | 平台占比 |
| fee_rate | 0.2 | 联盟服务费占比 |
| sync_interval | 30 | 任务同步间隔,单位分钟 |
对接联盟接口时,常见做法是后端写一个定时任务,用app_id和当前时间戳生成签名,调用联盟任务列表接口。拿到任务后先写缓存,再写入任务表,任务分类关联后台设置的任务分类名称。这一步如果写成同步阻塞,会在联盟接口慢时把整个任务列表页面拖挂,我一般会改成异步队列,先返回“同步中”,等队列完成后在后台刷新增任务数。
4.2 积分商城兑换:锁库存与扣积分
积分商城的难点不是展示礼品,而是防止超发。用户积分够了,点击兑换,如果同一时间有两个人兑换最后一件礼品,必须只有一个人成功。代码上要在一开始就锁住商品库存:
async function exchangeGoods(userId, goodsId) { return await db.transaction(async (tx) => { const goods = await tx.select('goods', { id: goodsId }, { forUpdate: true }); if (goods.stock <= 0) { throw new Error('库存不足'); } const user = await tx.select('users', { id: userId }, { forUpdate: true }); if (user.points < goods.points_price) { throw new Error('积分不足'); } await tx.update('goods', { stock: goods.stock - 1 }, { id: goodsId }); await tx.update('users', { points: user.points - goods.points_price }, { id: userId }); await tx.insert('exchange_order', { user_id: userId, goods_id: goodsId, goods_name: goods.name, status: 'pending_ship' }); await tx.insert('points_flow', { user_id: userId, change: -goods.points_price, source: 'exchange', related_order: goodsId }); }); }FOR UPDATE在读用户和读商品时会锁住对应行,直到事务提交。注意这里的points_flow不是可选的,积分明细页面全靠它展示,用户问“积分怎么少了”时,查看points_flow比查看users.points有用得多。积分商品可以设置上架/下架,但下架不影响已生成的兑换订单继续发货。
4.3 积分来源与任务信用分的联动
积分从哪来?最常见的是任务返佣金额按比例转成积分,比如用户完成任务获得 1 元返佣,同时得到 1 积分;也可以设置为每次任务审核通过固定获得积分。“积分商城提高了用户粘性”落到代码上,就是把积分变成用户余额之外的第二种资产,并且这种资产可以消耗、可以过期。源码里的“其他设置”中应该有一个“任务信用分设置”,信用分和积分是两个体系:信用分影响能不能接某些高佣金任务,积分决定能兑什么礼品。
信用分低于阈值时,用户点击任务应被拦截。拦截逻辑可以放在 Vue 端的全局拦截器里,但更可靠的是后端在领取任务接口再校验一次:
async function beforeClaim(userId, taskId) { const user = await getUserById(userId); const task = await getTaskById(taskId); if (user.creditScore < task.min_credit_score) { return { code: 403, msg: '信用分不足,无法领取该任务' }; } return { code: 0 }; }这里的min_credit_score可以不是在每个任务上单独配置,而是后台“任务信用分设置”里配置的全局最小值。有些任务要求更高,可以在发布任务时单独填。
4.4 幻灯片任务接口与分类导航的 Vue 应用
“后台根据分类调用到幻灯片任务接口”这句话听起来绕,其实功能就是首页轮播图。后台配置任务分类名称和导航图片后,前端用分类 ID 拉取对应的幻灯片任务:
async function loadSlides(categoryId) { const { data } = await api.get('/task/slides', { params: { category_id: categoryId } }); this.slides = data.map(item => ({ id: item.id, image: item.nav_image, url: `/task/${item.id}` })); }分类导航图片和幻灯片任务的绑定通常放在后台的任务分类表里,前端根据category_id请求。Vue 项目里这里要决定是用img.src直接渲染还是用懒加载指令,我建议用懒加载,因为首页通常会加载五六张 banner 图,不懒加载会让首屏白屏一秒左右。
5. 部署与验证:把这套返佣逻辑真正跑起来
5.1 Vue 环境配置与打包
拿到源码后,前端部分第一件事是确认 Node 版本。如果直接npm install报错,常见原因不是 Vue 本身,而是包版本和 Node 18/20 不兼容。我的做法是保持源码里的package.json不动,用 Node 16 环境安装一次,再把整个node_modules作为缓存目录。安装依赖后:
npm install cp .env.development .env.production sed -i 's#http://localhost:8080#https://api.example.com#g' .env.production npm run build.env.production里的VUE_APP_API_BASE_URL是前端请求后端的根地址,如果用相对路径/api,需要在 nginx 里加反向代理。如果你遇到 vue 打包后布局异常,先检查 publicPath 是/还是./,部署在二级目录时要用./,否则 css 和 js 按根路径加载会全部 404。Vue Router 如果是 history 模式,nginx 需要把不存在的路径都 try_files 到 index.html:
location / { try_files $uri $uri/ /index.html; }5.2 后台配置项核对清单
后台“其他设置”建议在测试环境全部填好再上生产:
| 配置项 | 建议值 | 注意点 |
|---|---|---|
| 最低提现 | 10 元 | 低于这个值不能提交提现申请 |
| 最低单个任务发布佣金 | 0.3 元 | 防止有人发 0.01 元任务刷量 |
| 每日提现次数 | 1 次 | 超出时提交按钮置灰 |
| 任务信用分阈值 | 60 分 | 低于阈值时禁止领取高等级任务 |
| 任务审核时间 | 30 分钟 | 影响自动审核/提醒定时任务 |
配置项之间是有依赖的。比如最低提现设置成 50,但用户平均返佣只有 1 元,那么至少要完成 50 个任务才能提现,留存压力会很大。最好在积分商城配小额礼品,用户积分够 1 块钱也能兑换,不会因为提现门槛高而流失。
5.3 用一条测试任务验证返佣闭环
最后分享一个每次部署都会做的验证方法:注册两个账号 A 和 B,让 B 的邀请人填 A 的邀请码,A 和 B 的会员等级都设成普通。在后台发布一个返佣 1 元的任务,B 领取、提交、管理员审核通过,然后查数据库:
SELECT o.id AS order_id, o.status, f.user_id, f.amount FROM task_order o LEFT JOIN fund_flow f ON f.order_id = o.id AND f.flow_type = 'rebate' WHERE o.user_id = :bUserId;如果查出的结果里o.status = 'finished',并且f.amount的数值符合后台配置的一级分销比例,那普通返佣链路就是通的。再把 A 升级成银牌会员,重复一遍任务,看task_order.task_price_snapshot是否变成会员价格。这一连串验证做完,基本可以确定这套任务悬赏源码的返佣模型没有漏。
本文还有配套的精品资源,点击获取