1. 项目背景与需求分析
农贸市场作为城乡居民日常生活的重要场所,其摊位租赁管理一直面临着诸多痛点。传统的手工登记方式效率低下,容易出错,且难以应对复杂的租赁规则和费用计算。我曾参与过多个农贸市场的信息化改造项目,亲眼目睹过管理员们用Excel表格甚至纸质账本管理数百个摊位的混乱场景。
这个Python+Vue的农贸市场摊位租赁管理系统,正是为了解决以下核心问题而设计:
- 租赁信息不透明导致的纠纷(如摊位到期未提醒引发的续租争议)
- 手工计算租金易出错(特别是分段计价、节假日优惠等复杂场景)
- 商户信息分散难以统一管理(健康证到期、营业执照备案等)
- 市场方缺乏数据支撑决策(哪些区域更受欢迎、空置率分析等)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与架构设计
2.1 为什么选择Python+Django后端
在对比了多种技术方案后,我们最终采用Django作为后端框架,主要基于以下考量:
- 快速开发:Django自带的Admin后台可以立即生成基础管理界面,配合DRF(Django REST Framework)能快速构建API
- 数据建模优势:农贸市场的租赁业务涉及商户、摊位、合同、缴费等多个关联实体,Django的ORM能清晰表达这些关系
- 报表生成需求:需要频繁生成租金统计、空置率等报表,Python的Pandas库在这方面有天然优势
典型模型设计示例:
python复制class Stall(models.Model):
ZONE_CHOICES = [('A','蔬菜区'),('B','肉类区'),('C','水产区')]
code = models.CharField(max_length=10, unique=True) # 如"A-012"
zone = models.CharField(max_length=1, choices=ZONE_CHOICES)
size = models.DecimalField(max_digits=5, decimal_places=2) # 平方米
base_price = models.DecimalField(max_digits=8, decimal_places=2)
is_rented = models.BooleanField(default=False)
class LeaseContract(models.Model):
stall = models.ForeignKey(Stall, on_delete=models.PROTECT)
merchant = models.ForeignKey(Merchant, on_delete=models.PROTECT)
start_date = models.DateField()
end_date = models.DateField()
payment_cycle = models.PositiveSmallIntegerField() # 缴费周期(天)
2.2 Vue前端的技术考量
前端采用Vue 3 + Element Plus的组合,主要解决以下场景:
- 多角色视图分离:管理员、商户、市场工作人员看到的界面和功能完全不同
- 复杂表单处理:租赁合同涉及多级联动选择(区域→摊位类型→具体摊位)
- 可视化看板:需要展示摊位分布热力图、租金收入趋势等图表
一个典型的摊位选择组件实现:
vue复制<template>
<el-cascader
v-model="selectedStall"
:options="zoneOptions"
:props="cascaderProps"
@change="handleStallChange"
/>
</template>
<script setup>
const zoneOptions = ref([
{
value: 'A',
label: '蔬菜区',
children: [] // 动态加载该区域下的可用摊位
},
// 其他区域...
])
// 获取指定区域下的可用摊位
const loadStalls = async (zone) => {
const { data } = await axios.get(`/api/stalls/?zone=${zone}&available=true`)
return data.map(item => ({
value: item.id,
label: `${item.code} (${item.size}㎡)`
}))
}
</script>
3. 核心功能实现细节
3.1 智能摊位分配算法
农贸市场常遇到热门摊位争抢问题,我们开发了基于优先级的分配算法:
- 老商户优先:连续租赁3年以上的商户可获得原摊位优先续租权
- 品类平衡:防止同一区域出现过多同类摊位(如5个卖豆腐的摊位扎堆)
- 特殊需求适配:水产区需要给排水设施,熟食区需要电力容量检查
算法核心逻辑:
python复制def allocate_stall(merchant, preferred_zone=None):
# 检查老商户优先权
if merchant.years_leased >= 3 and merchant.last_stall.is_available:
return merchant.last_stall
# 获取符合条件的摊位
candidates = Stall.objects.filter(
zone=preferred_zone or merchant.business_type.zone,
is_rented=False,
special_requirements__lte=merchant.special_requests
)
# 品类平衡过滤
same_business_count = Stall.objects.filter(
zone=preferred_zone,
current_merchant__business_type=merchant.business_type
).count()
if same_business_count >= ZONE_MAX_SAME_BUSINESS:
candidates = candidates.exclude(
current_merchant__business_type=merchant.business_type
)
# 返回面积最接近商户需求的摊位
return candidates.annotate(
diff=Abs(F('size') - merchant.required_size)
).order_by('diff').first()
3.2 费用计算引擎
农贸市场租金往往包含多种计算规则:
- 基础租金:按摊位面积×单价
- 附加费用:水电费、垃圾处理费等
- 优惠政策:早鸟优惠、连续租赁折扣
- 违约金:合同未到期退租的计算
我们设计了规则引擎来处理这种复杂性:
python复制class FeeCalculator:
def __init__(self, contract):
self.contract = contract
self.rules = [
BaseRentRule(),
UtilityFeeRule(),
EarlyBirdDiscountRule(),
LoyaltyDiscountRule()
]
def calculate(self):
total = 0
breakdown = []
for rule in self.rules:
amount = rule.apply(self.contract)
if amount:
total += amount
breakdown.append({
'name': rule.name,
'amount': amount
})
return {
'total': total,
'breakdown': breakdown,
'due_date': self.contract.start_date + timedelta(days=7)
}
4. 实战中的经验与优化
4.1 性能优化要点
在用户量较大的市场部署时,我们遇到了以下性能问题及解决方案:
问题1:摊位列表加载慢
- 现象:打开摊位管理页面需要5秒以上
- 根因:一次性返回所有摊位数据(包括历史租赁记录)
- 解决:
python复制# 改造前 stalls = Stall.objects.all() # 改造后 stalls = Stall.objects.only('code', 'zone', 'size', 'is_rented')
问题2:合同生成PDF耗时
- 现象:高峰期多人同时下载合同时服务器负载高
- 方案:
- 使用Celery异步生成
- 对相同模板的合同启用缓存
- 前端显示"生成中"状态
4.2 数据安全实践
系统处理大量商户敏感信息,我们采取了这些安全措施:
- 字段级加密:身份证号、银行卡号等使用AES加密存储
- 操作审计:关键操作记录修改前后的数据差异
python复制@receiver(pre_save, sender=LeaseContract) def log_contract_change(sender, instance, **kwargs): if instance.pk: original = LeaseContract.objects.get(pk=instance.pk) changes = {} for field in ['start_date', 'end_date', 'payment_amount']: if getattr(original, field) != getattr(instance, field): changes[field] = { 'old': getattr(original, field), 'new': getattr(instance, field) } if changes: AuditLog.objects.create( user=current_user(), model='LeaseContract', record_id=instance.pk, changes=json.dumps(changes) )
5. 扩展功能与二次开发
5.1 微信小程序集成
为方便商户自助查询,我们扩展了微信小程序功能:
- 移动端看板:商户可查看自己的缴费记录、合同状态
- 扫码报修:摊位设施问题扫码即可提交维修申请
- 消息推送:合同到期前30天开始微信提醒
小程序与后端交互的关键代码:
javascript复制// 获取商户当月费用明细
function fetchMonthlyFee(month) {
return wx.request({
url: 'https://api.example.com/miniapp/fee',
method: 'GET',
data: {
token: getApp().globalData.token,
month: month
},
success(res) {
if (res.data.code === 200) {
return res.data.data
}
}
})
}
5.2 数据可视化实践
为市场管理方提供的决策支持功能:
- 热力图分析:使用ECharts展示各区域摊位使用率
vue复制<template> <div ref="heatmap" style="width:800px;height:500px"></div> </template> <script setup> import * as echarts from 'echarts' onMounted(async () => { const { data } = await axios.get('/api/stalls/heatmap') const chart = echarts.init(heatmap.value) chart.setOption({ tooltip: {}, visualMap: { min: 0, max: 100, inRange: { color: ['#50a3ba', '#eac736', '#d94e5d'] } }, series: [{ type: 'heatmap', data: data.map(item => [item.x, item.y, item.value]), // ...其他配置 }] }) }) </script> - 收入预测:基于历史数据的季节性ARIMA模型
- 商户画像:分析各品类商户的经营周期和续约率
6. 部署与运维建议
6.1 服务器配置方案
根据项目规模推荐不同部署方案:
| 市场规模 | 商户数量 | 推荐配置 | 备注 |
|---|---|---|---|
| 小型 | <100户 | 2核4G云服务器 | 可共用MySQL实例 |
| 中型 | 100-300户 | 4核8G+独立数据库 | 建议配置Redis缓存 |
| 大型 | >300户 | 集群部署+负载均衡 | 需要专业运维支持 |
6.2 日常维护要点
-
数据备份策略:
- 每日凌晨自动全量备份到OSS
- 保留最近7天的备份副本
- 每月1号进行灾难恢复演练
-
日志分析技巧:
bash复制# 查看最近1小时的错误日志 grep "ERROR" /var/log/market-system.log | awk -v d1="$(date --date='1 hour ago' +'%Y-%m-%d %H:%M')" -v d2="$(date +'%Y-%m-%d %H:%M')" '$0 > d1 && $0 < d2' -
性能监控指标:
- API平均响应时间(<500ms)
- 数据库连接池使用率(<80%)
- 并发用户数预警阈值(根据服务器配置调整)
这个系统在实际部署后,最让我意外的是商户们对移动端功能的接受度远超预期。许多原本习惯线下办事的中老年商户,经过简单培训后都能熟练使用小程序查看账单、提交申请。这也提醒我们,在传统行业信息化过程中,不能低估用户的适应能力,关键是要设计符合他们使用习惯的交互方式。
