首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >CodeBuddy开发记丨医药商城小程序系统重构实战:佣金明细、退款流程与菜单治理

CodeBuddy开发记丨医药商城小程序系统重构实战:佣金明细、退款流程与菜单治理

原创
作者头像
元岳科技
发布2026-09-08 17:21:35
发布2026-09-08 17:21:35
170
举报
文章被收录于专栏:小程序搭建小程序搭建

最近对独立部署的医药商城 小程序做了一些功能的后端重构,涉及佣金计算明细、药品退款流程、后台菜单结构三块。这篇文章记录一下方案设计和踩过的坑。

注:本次修改全部使用 codebuddy二开,先读取整个项目的逻辑,再喂给AI,让AI提出疑问,解答完所有的疑问,再让AI开始改代码,基本上准确率能达到80%意思,后续测试简单修改,即可。

一、佣金计算明细:从黑盒到可审计

问题背景

原来的佣金模块只存最终金额,运营和分销商看不到计算过程。分销商问"这笔订单为什么是这个数",只能人工翻订单、翻比例、拿计算器现场算。订单量一大,客诉成本很高。

方案设计

核心思路是下单时固化计算快照,页面只做只读展示。具体做法:

下单环节计算佣金时,同步生成可读的公式描述(如"实付金额 × 分销比例")存入快照字段

收货/结算时直接读取快照写流水,不再重新计算

前端弹窗渲染快照中的公式和明细,不参与任何计算逻辑

这样做的好处是:金额可解释、计算过程不可篡改、前端只负责展示。

代码语言:txt
复制
     * 推广订单明细列表查询
     * @param array $where
     * @param string $nickname
     * @param int $page
     * @param int $limit
     * @return array
     */
    public function getCommissionOrderList(array $where, string $nickname, int $page, int $limit): array
    {
        $query = $this->getModel()->alias('o')
            ->where(function ($q) {
                $q->where('o.spread_uid', '>', 0)
                  ->whereOr('o.brokerage_status', '<>', '');
            });
        if (!empty($where['brokerage_status'])) {
            $query->where('o.brokerage_status', $where['brokerage_status']);
        }
        if (!empty($where['brokerage_status_not_normal'])) {
            $query->where('o.brokerage_status', '<>', 'normal')->where('o.brokerage_status', '<>', '');
        }
        if (!empty($where['order_id'])) {
            $query->where('o.order_id', 'like', '%' . $where['order_id'] . '%');
        }
        // 分销员昵称搜索:通过 user 表关联
        if (!empty($nickname)) {
            $query->join('eb_user u', 'o.spread_uid = u.uid', 'LEFT')
                  ->where('u.nickname', 'like', '%' . $nickname . '%');
        }
        return $query->field('o.id,o.order_id,o.spread_uid,o.one_brokerage,o.pay_price,o.brokerage_status')
            ->page($page, $limit)
            ->order('o.id', 'desc')
            ->select()
            ->toArray();
    }

    /**
     * 推广订单明细总数
     * @param array $where
     * @param string $nickname
     * @return int
     */
    public function getCommissionOrderCount(array $where, string $nickname): int
    {
        $query = $this->getModel()->alias('o')
            ->where(function ($q) {
                $q->where('o.spread_uid', '>', 0)
                  ->whereOr('o.brokerage_status', '<>', '');
            });
        if (!empty($where['brokerage_status'])) {
            $query->where('o.brokerage_status', $where['brokerage_status']);
        }
        if (!empty($where['brokerage_status_not_normal'])) {
            $query->where('o.brokerage_status', '<>', 'normal')->where('o.brokerage_status', '<>', '');
        }
        if (!empty($where['order_id'])) {
            $query->where('o.order_id', 'like', '%' . $where['order_id'] . '%');
        }
        if (!empty($nickname)) {
            $query->join('eb_user u', 'o.spread_uid = u.uid', 'LEFT')
                  ->where('u.nickname', 'like', '%' . $nickname . '%');
        }
        return $query->count();
    }

    /**
     * 推广订单明细导出(不分页)
     * @param array $where
     * @param string $nickname
     * @return array
     */
    public function getCommissionOrderListAll(array $where, string $nickname): array
    {
        $query = $this->getModel()->alias('o')
            ->where(function ($q) {
                $q->where('o.spread_uid', '>', 0)
                  ->whereOr('o.brokerage_status', '<>', '');
            });
        if (!empty($where['brokerage_status'])) {
            $query->where('o.brokerage_status', $where['brokerage_status']);
        }
        if (!empty($where['brokerage_status_not_normal'])) {
            $query->where('o.brokerage_status', '<>', 'normal')->where('o.brokerage_status', '<>', '');
        }
        if (!empty($where['order_id'])) {
            $query->where('o.order_id', 'like', '%' . $where['order_id'] . '%');
        }
        if (!empty($nickname)) {
            $query->join('eb_user u', 'o.spread_uid = u.uid', 'LEFT')
                  ->where('u.nickname', 'like', '%' . $nickname . '%');
        }
        return $query->field('o.id,o.order_id,o.spread_uid,o.one_brokerage,o.pay_price,o.brokerage_status')
            ->order('o.id', 'desc')
            ->select()
            ->toArray();
    }

踩坑记录

快照字段最初放在订单表,后来发现退款场景需要回看历史比例,拆到独立的佣金明细表更合理

分销比例如果支持后台随时调整,必须取"下单时的比例"而非"当前比例",否则历史订单会被静默改算

二、退款流程:药品订单强制整单退货

问题背景

药品电商的退款和普通电商不同,有两个硬约束:一是已发货必须退货退款(货不回来钱不退),二是不能单品退(防止拆单套利)。原来的实现里前端可以控制退款类型和商品选择,存在绕过风险。

方案设计

核心原则:退款权限是服务端状态机,不是UI 开关。

未发货状态:仅退款,原逻辑不变

已发货状态:后端强制校验refund_type=2(退货退款),且cart_ids必须覆盖整单有效商品

前端对已发货订单锁死退款方式切换 ,渲染合规提醒条

代码语言:txt
复制
     * 整单退款强校验:仅允许整单退款,禁止单品/部分退款
     * 有效商品集合:订单 order_id 匹配、剩余可退(surplus_num>0)的全部 cart 行(含赠品,不过滤 pid)
     * 校验通过后强制清空 cart_ids,走 applyRefund 整单分支,确保佣金正确置 0
     * @param mixed $order 订单信息(Model 对象或数组均可)
     * @param array $cart_ids 提交的退款商品集合(引用,校验通过将被置空以走整单分支)
     * @throws ApiException
     */
    protected function checkWholeOrderRefund($order, array &$cart_ids)
    {
        // 订单可能为 Model 对象或数组,统一转数组便于访问
        if (is_object($order) && method_exists($order, 'toArray')) {
            $order = $order->toArray();
        }
        // 空集合:后台/客服整单退款入口,直接放行
        if (empty($cart_ids)) {
            return;
        }
        /** @var StoreOrderCartInfoServices $cartInfoServices */
        $cartInfoServices = app()->make(StoreOrderCartInfoServices::class);
        // 有效集合:该订单下剩余可退(surplus_num>0)的全部 cart 行,含赠品,不过滤 pid;以主键 id 为键
        $validCarts = $cartInfoServices->getCartColunm(['oid' => $order['id']], 'id,cart_num,surplus_num,refund_num', 'id');
        $validIds = [];
        $surplusMap = [];
        foreach ($validCarts as $cid => $cart) {
            if (($cart['surplus_num'] ?? 0) > 0) {
                $validIds[] = $cid;
                $surplusMap[$cid] = (int)$cart['surplus_num'];
            }
        }
        $submitIds = array_column($cart_ids, 'cart_id');
        // 提交集合必须与有效集合完全匹配(顺序无关),防止漏退/部分退(含赠品)
        if (count($submitIds) !== count($validIds)
            || array_diff($submitIds, $validIds)
            || array_diff($validIds, $submitIds)) {
            throw new ApiException('本订单仅支持整单退款,请选择全部商品申请退款。');
        }
        // 每项申请退款数量必须等于该行剩余可退数量(强制整件全退,不允许同一商品仅退部分数量)
        $cartMap = array_column($cart_ids, 'cart_num', 'cart_id');
        foreach ($validIds as $cid) {
            if ((int)($cartMap[$cid] ?? 0) !== $surplusMap[$cid]) {
                throw new ApiException('本订单仅支持整单退款,请选择全部商品申请退款。');
            }
        }
        // 校验通过:强制按整单方式退款(清空,走 applyRefund 整单分支,佣金正确置 0)
        $cart_ids = [];
    }

关键校验点

delivery_status判断必须在服务端做,不能信任前端传入的状态

cart_id集合校验用排序后比对,防止漏选或多选

校验失败直接抛异常,不走"前端disabled所以不会触发"的逻辑

三、后台菜单迁移:推广订单明细独立化

问题背景

推广订单明细原来挂在佣金记录页面的el-tab里,运营反馈"找不到入口"。本质问题是菜单树设计不合理——一个叶子菜单承载了两个业务含义。

改法

前端抽独立组件,复用原接口,业务零改动

路由新增子项,权限标识独立

菜单表通过SQL直接插入新节点,清缓存生效

代码语言:txt
复制
    {
      path: 'finance/commission_order',
      name: `${pre}commissionOrder`,
      meta: {
        auth: ['finance-finance-commission-order'],
        title: '推广订单明细',
      },
      component: () => import('@/pages/finance/commissionOrder/index'),
    },
    {

经验

系统菜单是数据库驱动的,界面上配不了完整结构。遇到"页面藏太深"的问题,优先改菜单树pid关系,别在组件层面打补丁。

四、顺带修的几个小问题

cart_id列与主键id不匹配导致的整单校验误判

多商品订单名称拼接后单元格溢出,前端split拆分展示

退款上传提示文案和按钮文案按场景区分

总结

医药电商系统的难点不在功能复杂度,而在合规约束和可解释性。佣金要能说清"为什么是这个数",退款要能证明"系统不允许违规操作",菜单要让运营"不用培训就能找到"。把这三件事做扎实,后面的药品参数、处方药管控就只是字段层面的扩展,架构上不用再动。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档