大家好,我是Java1234_小锋老师,分享一套锋哥原创的基于Python的会员管理系统(带微信小程序会员端)(FastAPI+Vue3)优质版

项目介绍

随着移动互联网与数字化运营的快速发展,会员制已成为零售、餐饮、生活服务等行业提升客户黏性与复购率的重要手段。传统依赖纸质会员卡或分散表格登记的管理模式,存在信息滞后、统计困难、等级折扣难以自动执行、会员端触达不便等问题。因此,设计并实现一套前后端分离、移动端可访问的会员管理系统,对中小型商户实现精细化运营具有现实意义。

本文设计并实现了“基于Python的会员管理系统(带微信小程序端)”。系统采用B/S架构与前后端分离思想:后端基于Python语言与FastAPI框架构建RESTful接口,使用SQLAlchemy操作MySQL数据库(库名db_member),并结合JWT完成管理员与会员双端身份认证;管理后台基于Vue3、Vue Router、Pinia与Element Plus实现,首页集成ECharts数据统计图表;微信小程序端面向会员用户,提供用户名密码登录、注册、公告浏览、资产查看、头像与个人资料修改、密码修改等功能。业务上覆盖会员档案、会员等级与折扣、商品管理、充值、消费、积分获取与兑换、公告发布以及管理员与角色管理等核心场景,并实现消费折扣计算、余额扣减、积分累计与自动升级等关键业务闭环。

系统经过功能测试验证,各模块运行稳定,接口响应正常,能够满足本科毕业设计对完整性、规范性与实用性的要求。实践表明,Python与FastAPI适合快速构建高可读性的后端服务,Vue3适合构建交互友好的管理端,微信小程序则能有效降低会员使用门槛。论文同时给出了E-R图、功能结构图、时序图与数据表字段设计,并对关键功能附核心代码说明,便于复现与二次开发。

源码下载 

链接: https://pan.baidu.com/s/1hbcKeOPblLjWPn4royqf3A?pwd=1234
提取码: 1234

系统展示

核心代码

"""
消费业务服务
对齐 Java ConsumeServiceImpl.doConsume
"""
from decimal import Decimal, ROUND_DOWN, ROUND_HALF_UP

from sqlalchemy.orm import Session

from app.common.exception import BusinessException
from app.models.consume import Consume
from app.models.member import Member
from app.models.member_level import MemberLevel
from app.models.points_record import PointsRecord
from app.models.product import Product


class ConsumeService:
    """消费服务类"""

    @staticmethod
    def do_consume(db: Session, consume: Consume) -> None:
        """
        执行会员消费:按等级折扣扣费、扣库存、增加积分并记录
        :param db: 数据库会话
        :param consume: 消费信息
        """
        member = (
            db.query(Member)
            .filter(Member.id == consume.member_id, Member.deleted == 0)
            .first()
        )
        if member is None:
            raise BusinessException("会员不存在")
        if member.status is not None and member.status == 0:
            raise BusinessException("会员已冻结,无法消费")

        amount = consume.amount
        # 若选择了商品,以商品价格为准并扣库存
        if consume.product_id is not None:
            product = (
                db.query(Product)
                .filter(Product.id == consume.product_id, Product.deleted == 0)
                .first()
            )
            if product is None:
                raise BusinessException("商品不存在")
            if product.status is not None and product.status == 0:
                raise BusinessException("商品已下架")
            if product.stock is not None and product.stock <= 0:
                raise BusinessException("商品库存不足")
            amount = product.price
            product.stock = product.stock - 1
            db.add(product)

        if amount is None or amount <= 0:
            raise BusinessException("消费金额必须大于0")

        # 按会员等级计算折扣
        discount = Decimal("1")
        if member.level_id is not None:
            level = (
                db.query(MemberLevel)
                .filter(MemberLevel.id == member.level_id, MemberLevel.deleted == 0)
                .first()
            )
            if level is not None and level.discount is not None:
                discount = Decimal(str(level.discount))

        real_amount = (amount * discount).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
        balance = member.balance if member.balance is not None else Decimal("0")
        if balance < real_amount:
            raise BusinessException("会员余额不足")

        new_balance = balance - real_amount
        points_gained = int(real_amount.to_integral_value(rounding=ROUND_DOWN))
        current_points = member.points if member.points is not None else 0
        new_points = current_points + points_gained

        member.balance = new_balance
        member.points = new_points
        ConsumeService._upgrade_level(db, member, new_points)
        db.add(member)

        consume.amount = amount
        consume.real_amount = real_amount
        consume.points_gained = points_gained
        consume.balance_after = new_balance
        consume.deleted = 0
        db.add(consume)

        # 记录积分获取
        if points_gained > 0:
            record = PointsRecord(
                member_id=member.id,
                change_points=points_gained,
                type="获取",
                remark="消费获得积分",
                deleted=0,
            )
            db.add(record)

        db.commit()

    @staticmethod
    def _upgrade_level(db: Session, member: Member, new_points: int) -> None:
        """
        根据积分自动升级会员等级
        :param db: 数据库会话
        :param member: 会员
        :param new_points: 最新积分
        """
        levels = (
            db.query(MemberLevel)
            .filter(MemberLevel.deleted == 0)
            .order_by(MemberLevel.upgrade_points.desc())
            .all()
        )
        for level in levels:
            if level.upgrade_points is not None and new_points >= level.upgrade_points:
                member.level_id = level.id
                break
<template>
  <div class="page-card">
    <div class="search-bar">
      <el-select v-model="query.memberId" placeholder="选择会员" clearable filterable style="width: 200px">
        <el-option v-for="m in members" :key="m.id" :label="`${m.name}(${m.cardNo})`" :value="m.id" />
      </el-select>
      <el-button type="primary" @click="loadData"><el-icon><Search /></el-icon>查询</el-button>
      <el-button @click="query.memberId=null; query.pageNum=1; loadData()"><el-icon><Refresh /></el-icon>重置</el-button>
    </div>
    <div class="toolbar">
      <el-button type="primary" @click="openDialog()"><el-icon><Plus /></el-icon>会员消费</el-button>
    </div>
    <el-table :data="tableData" stripe border style="width: 100%">
      <el-table-column prop="cardNo" label="会员卡号" min-width="120" />
      <el-table-column prop="memberName" label="会员姓名" min-width="100" />
      <el-table-column prop="productName" label="商品" min-width="120" />
      <el-table-column prop="amount" label="原价" min-width="90" />
      <el-table-column prop="realAmount" label="实付" min-width="90" />
      <el-table-column prop="pointsGained" label="获得积分" min-width="90" />
      <el-table-column prop="balanceAfter" label="消费后余额" min-width="110" />
      <el-table-column prop="operator" label="操作人" min-width="90" />
      <el-table-column prop="createTime" label="消费时间" min-width="160" />
      <el-table-column label="操作" min-width="90" fixed="right">
        <template #default="{ row }">
          <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <div class="pagination-wrap">
      <el-pagination v-model:current-page="query.pageNum" v-model:page-size="query.pageSize" :total="total"
        :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next" @size-change="loadData" @current-change="loadData" />
    </div>

    <el-dialog v-model="dialogVisible" title="会员消费" width="480px" destroy-on-close>
      <el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
        <el-form-item label="会员" prop="memberId">
          <el-select v-model="form.memberId" filterable placeholder="请选择会员" style="width: 100%">
            <el-option v-for="m in members" :key="m.id" :label="`${m.name}(${m.cardNo}) 余额:${m.balance}`" :value="m.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="商品" prop="productId">
          <el-select v-model="form.productId" filterable placeholder="请选择商品" style="width: 100%">
            <el-option v-for="p in products" :key="p.id" :label="`${p.productName} ¥${p.price}`" :value="p.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="form.remark" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" :loading="saving" @click="handleSave">确认消费</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
/**
 * 消费管理页面
 */
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/request'

const tableData = ref([])
const members = ref([])
const products = ref([])
const total = ref(0)
const dialogVisible = ref(false)
const saving = ref(false)
const formRef = ref()
const query = reactive({ pageNum: 1, pageSize: 10, memberId: null })
const form = reactive({ memberId: null, productId: null, remark: '' })
const rules = {
  memberId: [{ required: true, message: '请选择会员', trigger: 'change' }],
  productId: [{ required: true, message: '请选择商品', trigger: 'change' }]
}

async function loadData() {
  const res = await request.get('/api/consume/list', { params: query })
  tableData.value = res.data.records
  total.value = res.data.total
}

async function loadOptions() {
  const [m, p] = await Promise.all([
    request.get('/api/member/all'),
    request.get('/api/product/all')
  ])
  members.value = m.data
  products.value = p.data
}

function openDialog() {
  Object.assign(form, { memberId: null, productId: null, remark: '' })
  dialogVisible.value = true
}

async function handleSave() {
  await formRef.value.validate()
  saving.value = true
  try {
    await request.post('/api/consume', form)
    ElMessage.success('消费成功')
    dialogVisible.value = false
    loadData()
    loadOptions()
  } finally { saving.value = false }
}

function handleDelete(row) {
  ElMessageBox.confirm('确定删除该消费记录吗?', '提示', { type: 'warning' }).then(async () => {
    await request.delete(`/api/consume/${row.id}`)
    ElMessage.success('删除成功')
    loadData()
  }).catch(() => {})
}

onMounted(() => { loadOptions(); loadData() })
</script>

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐