1. 项目概述与技术选型
1.1 这个农场管理系统到底在解决什么问题
先坦白一下,我最初接手这套系统时,农场那边的需求文档只有两页纸,核心诉求就一句话:把农事台账从纸质记录和Excel表里搬到系统里。当时办公桌上堆着好几本手写记录本,里面有播种日期、施肥批次、农药用量、采收时间,翻起来费劲,统计起来更是灾难。所以这套基于Python和Django的农场管理系统,本质上不是一个炫技项目,而是围绕“地块—作物—农事操作—投入品—人员”这条主线,把农场的日常管理动作线上化、可追溯、可统计。
这套系统适合谁参考?如果你是正在做毕业设计的学生,需要一个功能完整、代码结构清晰、能讲清楚设计思路的Web项目,这套系统是一个很典型的多模块业务系统;如果你在帮小型农场、合作社做数字化改造,这套系统的模块划分和数据模型设计可以直接当原型用。它的业务边界刚好卡在“够用”和“不臃肿”之间——没有一堆用不上的功能,但地块管理、作物档案、农事记录、库存管理、统计报表这些刚需全覆盖了。
技术上就是经典的Python + Django 全家桶方案,前端用Django模板加一点简单的JavaScript和CSS,后台管理直接用Django自带的Admin,数据存储默认SQLite,部署到生产环境时换成MySQL或PostgreSQL都行。整体难度属于中等偏下,只要你会Python基础语法,能看懂类、函数、ORM查询,这套系统的源码就能读得动。
1.2 为什么是Python+Django而不是其他组合
我在好几个项目里对比过不同的Web框架,最后给农场类管理系统选型时,Django几乎是首选。原因有三点,每一点都踩过坑才总结出来的。
第一,Django自带Admin后台。农场管理系统的使用者分两类:一类是日常操作的农场工人,他们需要简单明了的表单界面;另一类是农场主或管理员,他们需要看数据、导出报表、偶尔修正某条记错的农事操作。Django Admin几乎不用写代码就能满足第二类需求,注册一下模型就能得到一个可增删改查的后台,这个对业务系统来说节省的时间不是一星半点。你要是用Flask或者Express这类轻量框架,后台管理界面得自己造轮子,造到一半你就会回来找Admin。
第二,Django的ORM在处理农事数据这种“多条件组合查询”的场景下特别顺手。比如你要查“2023年5月之后、3号地块、用了某批次复合肥的追肥记录”,ORM写起来就是filter连起来,SQL由框架生成,不用手工拼接字符串,也不容易出SQL注入问题。农事记录往往要按日期范围、地块、作物类型、操作类型多维筛选,ORM这种链式查询的体验远好过手写SQL。
第三,Django的项目结构天然适合“功能模块多、逻辑边界清晰”的业务系统。一个农事操作可能涉及地块、作物、投入品、人员四个维度的数据,Django的app划分机制能把这些维度拆成独立模块,数据模型互不干扰,逻辑却可以通过外键和查询串起来。后面代码维护的时候,改库存模块不会动到地块模块,这对一个持续迭代的项目太重要了。
选型时也有人问过我,要不要前端做成Vue或React的分离式架构。我的建议是,如果团队里没有专业前端,这套系统老老实实用Django模板就够了。分离式架构意味着你要维护两套代码、处理跨域、设计接口文档,对一个小型管理系统来说是纯纯增加工作量。模板渲染的方案,服务端直接输出页面,数据在前端用简单JS控制交互,开发和维护成本都低一个量级。
2. 核心模块与数据模型设计
2.1 功能模块怎么划分才合理
农场管理系统这类项目,最忌讳的是把所有功能塞进一个app里。我的划分逻辑是从农场实际作业流程倒推的,一个操作动作对应一个模块,数据归属清晰,后面加功能也好扩展。
整个系统一共拆成六个app:地块管理、作物档案、农事记录、投入品库存、人员管理、统计报表。地块管理管的是“地在哪、有多大、什么土壤条件”;作物档案管的是“这块地种了什么、品种是什么、预计产量多少”;农事记录是核心,管的是“什么时间、谁、在哪块地、对哪种作物、做了什么操作、用了多少投入品”;投入品库存管的是“仓库里有多少种子、化肥、农药,出入库流水”;人员管理管的是农场工人信息、角色权限;统计报表则是把所有数据聚合起来,展示种植面积、任务完成量、投入品消耗等指标。
模块划分有个经验之谈:宁可拆多一个app,也不要都堆在一起。Django的app本身就是一个业务边界,边界越清晰,代码review和管理越轻松。而且app之间通过外键关联时,查询逻辑写在哪个app里都有理有据,不会出现“这个函数到底该放哪”的纠结。
2.2 数据表设计思路与关键字段
数据模型是整个系统的地基,这块我设计时反复推敲过。农场业务有个特点:数据之间的关联路径很长,一块地种过多种作物,一种作物经历多次农事操作,每次操作用到若干种投入品。如果不提前设计好外键关系,后面统计查询会写成噩梦。
核心模型有这么几个,我直接说关键字段:
地块表保存地块名称、编号、面积、土壤类型、当前状态。面积用DecimalField存,精确到小数点后两位,单位统一用亩,避免后面统计加总时单位不一致的尴尬。
作物档案表通过ForeignKey关联地块,另外记录作物品种、种植日期、预计采收日期、生长状态。这里需要注意,同一块地是轮作的,所以作物档案和历史要分开——每季种植都新建一条作物档案记录,而不是在地块表上改作物名。这样设计的好处是,查地块历史种植记录时直接按地块外键筛作物档案表就能出结果,不用额外做日志表。
农事操作表是这个系统里最核心的表,它几乎是个业务事实表。字段包括操作类型、操作时间、操作人员、关联的地块、关联的作物生长记录、使用的投入品、用量、备注。操作类型我用choices限定,比如整地、播种、灌溉、施肥、施药、采收等,方便统计和筛选。投入品用量用JSON字段还是独立关联表,我纠结过,最终选了独立关联表——一次操作可能用多种投入品,如果塞在一个字段里,后面统计库存消耗时根本没法查。
人员表直接复用需要引入的用户模型,另外扩展一个电话和角色字段。这里的角色不是Django Group那种复杂的权限体系,就是简单的“管理员”和“工人”两个选项,配套装饰器做视图函数级权限控制。
这套模型设计有一个贯穿始终的原则——每个表都保留创建时间和更新时间字段。农事操作经常会出现“昨天忘记记录今天补录”的情况,保留创建时间能查出来真实录入时间,方便审计。所有外键字段都要设置on_delete选项,Django默认报错要求显式声明,我统一设成PROTECT,防止误删关联数据。
2.3 用户登录与权限控制的实用方案
农场管理系统需要登录才能用,这是基本要求。我直接用Django自带的认证框架,没有自己写session管理,因为自带方案足够安全也足够成熟。
权限控制没有做得太复杂:用Django的装饰器实现两级权限。“管理员”角色的用户可以看所有页面,包括Admin后台;“工人”角色只能看自己负责的地块和农事任务记录。实现方式是在自定义视图函数上直接用装饰器判断用户角色的字段,不满足就跳转到登录页或返回403页面。
有个细节值得提醒:Django默认的User表里没有“角色”这一个自定义字段,需要通过扩展用户模型的方式加上。我采用的方法是新建一个Profile模型,用OneToOneField关联到User,再在登录成功后把用户角色存到session里,这样每次请求就不用查数据库了。登录逻辑一看就懂:验证用户名密码、检查是否被封禁、更新最后登录时间、跳转到对应首页。
Auth系统还有个容易被忽略的点:Django默认密码加密算法是会随时间变化的,从老版本迁移过来的项目,密码哈希会自动升级。所以千万注意不要在代码里手写MD5存密码,直接用自带机制,安全省心。
3. 关键代码实现与代码讲解
3.1 农事任务记录的增删改查是怎么实现的
农事任务记录是整个系统的核心操作,日常使用频率最高,我把这部分代码拆解清楚,其他模块照着写就行。
首先是models里的定义:
class FarmingTask(models.Model): OPERATION_CHOICES = [ ('plowing', '整地'), ('seeding', '播种'), ('irrigating', '灌溉'), ('fertilizing', '施肥'), ('pesticide', '施药'), ('harvesting', '采收'), ] farmland = models.ForeignKey(Farmland, on_delete=models.PROTECT, verbose_name='地块') crop = models.ForeignKey(CropRecord, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='作物') task_type = models.CharField(max_length=20, choices=OPERATION_CHOICES, verbose_name='操作类型') operator = models.ForeignKey(WorkerProfile, on_delete=models.SET_NULL, null=True, verbose_name='操作人员') description = models.TextField(blank=True, verbose_name='操作说明') task_date = models.DateField(verbose_name='操作日期') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')这里有几个设计细节:操作人员允许为空,因为农场有时候会临时雇人,还没录入系统;作物记录允许为空,比如整地操作发生在播种之前,还没有关联的作物;地块不允许为空,因为任何农事操作都必须有发生地点。每个字段的空值限制都是对着实际业务场景定的,不是随便设的。
然后是视图部分,我以新增任务为例讲一下写法:
def task_create(request): if request.method == 'POST': form = FarmingTaskForm(request.POST) if form.is_valid(): task = form.save(commit=False) task.created_by = request.user task.save() return redirect('task_list') else: form = FarmingTaskForm() return render(request, 'farming/task_form.html', {'form': form})这段代码大部分人看一眼就懂,但有个容易踩的坑藏在form.save(commit=False)这一步。如果你直接用form.save(),created_by字段会被遗漏,因为表单里根本没展示这个字段。先commit=False把模型实例拿出来,手动补上当前登录用户,再save进去——这是一个很常见的“谁创建的这条数据”审计需求的标准解法。
模板里的表单渲染,我直接用了Django的form.as_p,省事。但要注意,凡是表单涉及日期选择,浏览器端的input类型和Django默认渲染的TextInput会有冲突,我的做法是在forms.py里用DateInput并指定input_type='date',这样在移动端也能调出原生日期选择器:
class FarmingTaskForm(forms.ModelForm): task_date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'})) class Meta: model = FarmingTask fields = ['farmland', 'crop', 'task_type', 'operator', 'description', 'task_date']任务列表页面用了一个最基本的ORM查询:按日期倒序排列,然后分页展示。分页我直接用Django内置的Paginator,每页20条,模板里加上会话保持参数。数据量超过几万条之后,每次打开列表页都要全表查询,所以我还加了一层优化的余地:列表页默认只展示最近三个月的任务,更老的记录通过日期筛选器查看,这样既保证了响应速度,也满足了“查历史”的需求。
3.2 统计报表与数据驾驶舱的简洁实现
统计报表是农场主最看重的功能,也是我花时间最多的地方。需求说起来很简单:看这个月做了多少事、各地块种植面积多少、投入品消耗了多少。但用ORM把这几个数字聚合出来,有几个典型的坑。
先看最简单的场地统计:
from django.db.models import Sum, Count, Avg def dashboard(request): total_farmland_area = Farmland.objects.aggregate( total_area=Sum('area') )['total_area'] or 0 this_month_tasks = FarmingTask.objects.filter( task_date__year=current_year, task_date__month=current_month ).count() crop_type_count = CropRecord.objects.values('crop_type').annotate( total=Count('id') ).order_by('-total')aggregate和annotate的区别,很多新手搞混。aggregate是对整个查询集做聚合,返回一个字典;annotate是按某个字段分组后聚合,返回的还是查询集,可以在模板里继续循环。统计报表里这两个方法几乎都会用到,一定要理解透。
我在做“投入品消耗统计”时踩过一个坑:一次农事操作可能关联多个投入品,而每个投入品有独立的用量,用传统的values('task_date').annotate(sum_quantity=Sum('quantity'))分组,会导致日期分组下数据重复计算,因为JOIN出来的结果集行数已经膨胀了。最终的解决办法是先按农事任务聚合,再在Python侧计算投入品用量,或者直接用子查询。这个问题的本质是SQL的GROUP BY和DISTINCT语义,建议大家在设计统计报表时先把数据模型和查询的粒度搞清楚,不要急着写聚合代码。
图表展示我没有引入前端图表库,直接渲染成简单的HTML表格,再用CSS做了柱状图效果。不要觉得Low,实际使用中农场主关心的是数字对不对、走势明不明显,一个纯CSS的条形图已经能说明问题,没必要为了好看引入几百KB的图表库。
3.3 图片上传与附件管理
农事记录经常要拍照留档,比如病虫害照片、采收现场照片。Django处理图片上传有一套成熟的机制,但配置错了会出现“上传成功却在页面上显示不出来”的问题,所以我把这部分单独拿出来讲。
模型里这样定义图片字段:
class TaskAttachment(models.Model): task = models.ForeignKey(FarmingTask, on_delete=models.CASCADE, related_name='attachments') image = models.ImageField(upload_to='task_images/%Y/%m/') uploaded_at = models.DateTimeField(auto_now_add=True)关键在settings.py里的配置:
MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')这三个配置必须同时满足,少了哪一个都会出问题。MEDIA_ROOT决定文件存到磁盘的哪个目录,MEDIA_URL决定浏览器访问时的URL前缀。开发环境下你还必须在urls.py里加上一行:
from django.conf import settings from django.conf.urls.static import static urlpatterns = [ # 其他路由 ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)如果不加这一行,Django开发服务器不会自动服务media目录下的文件,上传的图片就无法通过URL访问,但你在后台看文件确实已经存到磁盘上了,这个现象很容易让人困惑。
表单模板里记得加一个属性——enctype="multipart/form-data"。很多人上传图片失败就是这个没写,Django框架在请求体里取不到文件数据,视图的request.FILES就是空的。我顺手写了一句form.is_valid()之后校验request.FILES是否为空,不为空才保存,避免用户连图都没选就提交。
图片上传之后要做压缩处理吗?我的建议是不要急着压缩,先限制上传大小。在表单里校验文件尺寸,超过5MB直接报错提示,比上传后再压缩更省事。因为农场的网络环境普遍一般,大图上传容易失败,限制大小是最有效的体验优化。
3.4 用Django Admin搭建管理后台
农场系统的运营后台我是完全依赖Django Admin的。Admin的价值是零代码提供一个可以增删改查的界面,但为了让运营人员用起来顺手,还是要做几个关键的配置。
首先是注册模型并展示关键字段:
@admin.register(FarmingTask) class FarmingTaskAdmin(admin.ModelAdmin): list_display = ['task_date', 'farmland', 'crop', 'task_type', 'operator', 'created_at'] list_filter = ['task_type', 'task_date'] search_fields = ['description', 'farmland__name'] date_hierarchy = 'task_date' autocomplete_fields = ['farmland', 'crop']list_display决定列表页显示哪些列;list_filter在右侧生成筛选面板,直接点选操作类型、日期范围,对管理者来说比搜索框直观得多;search_fields支持跨表搜索,比如farmland__name这种写法会生成一个关联查询,在搜索框输入地块名字就能查到这个地块的所有农事任务。
date_hierarchy这个配置非常推荐,它会在列表页顶部生成一个按年份、月份钻取的日期导航条。农事记录是强时间属性的数据,管理员经常要看“上个月记录了哪些事”,这个功能正好。
整个Admin后台还配了导出功能,用django-import-export这个库实现Excel导出,农场的财务阿姨每周都要导一次农事记录汇总表,没有导出功能她得自己对着屏幕抄一遍,太痛苦了。
4. 部署文档与生产环境实操
4.1 本地开发环境的完整搭建步骤
无论你是要运行这套源码,还是要自己二次开发,第一步都是把本地环境跑起来。我在这里把步骤写完整,每个坑都标注清楚。
创建虚拟环境并激活:
python -m venv venv source venv/bin/activate # Linux/macOS venv\Scripts\activate # Windows安装依赖:
pip install -r requirements.txtrequirements.txt里面至少要包含这些核心包:Django、Pillow(图片处理必须)、django-import-export(后台导出可选)、gunicorn(生产部署用)。版本号建议锁定主版本,比如Django==4.2.*,不要用最新版,因为第三方库的兼容性有时跟不上。我见过太多项目因为Django升了一个小版本,某个第三方库突然不兼容,排查起来非常头疼。
执行数据迁移:
python manage.py makemigrations python manage.py migratemakemigrations会扫描models.py, 生成迁移文件;migrate才是真正把表建到数据库里。第一次运行如果提示No changes detected,说明app没有注册进INSTALLED_APPS,检查一下settings.py。
创建超级管理员:
python manage.py createsuperuser启动开发服务器:
python manage.py runserver这里有个我每次都要强调的细节:Django的runserver在settings.py里DEBUG=True时才能自动加载静态文件和代码热更新,一旦你为了“模拟生产”把DEBUG改成False再runserver,会发现样式全丢了,那是因为开发服务器不会帮你处理collectstatic的结果。本地开发时请保持DEBUG=True。
4.2 服务器部署:Gunicorn + Nginx + Systemd
本地跑通只是第一步,真正上线部署才是考验。我采用的是Linux服务器上最经典的组合:Django + Gunicorn + Nginx + Systemd。数据库生产环境建议从SQLite切到PostgreSQL,原因很简单:SQLite不支持并发写,两个工人同时录入农事操作时会出现database is locked的报错。
生产环境的settings.py里必须做三处修改:DEBUG设为False、ALLOWED_HOSTS填上服务器IP或域名、数据库换为PostgreSQL并配置连接信息。
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'farm_db', 'USER': 'farm_user', 'PASSWORD': '强密码', 'HOST': 'localhost', 'PORT': '5432', } }收集静态文件:
python manage.py collectstatic --noinputcollectstatic会把所有app的静态文件拷贝到STATIC_ROOT指定的目录,这个目录就是Nginx要服务的前端文件。如果不做这一步,Django在生产模式下不会提供任何静态资源,你看到的就是一个没有CSS的裸页面。
Gunicorn是一个Python WSGI服务器,负责运行Django应用。用systemd来守护进程,让Gunicorn开机自启、崩溃自动拉起。
写一个systemd服务文件:
[Unit] Description=Gunicorn instance to serve farm project After=network.target [Service] User=www-data Group=www-data WorkingDirectory=/srv/farmproject Environment="PATH=/srv/farmproject/venv/bin" ExecStart=/srv/farmproject/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8001 farmproject.wsgi:application [Install] WantedBy=multi-user.targetworkers数量建议根据服务器CPU核心数来定,公式是2*CPU核数+1,4核服务器配9个worker。如果服务器配置很一般,就先用2个worker,不要贪多,worker越多内存占用越大,小内存机器反而会被榨干。
Nginx的配置核心是反代和静态文件:
server { listen 80; server_name your-domain.com; location /static/ { alias /srv/farmproject/static/; } location /media/ { alias /srv/farmproject/media/; } location / { proxy_pass http://127.0.0.1:8001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }改完配置记得:
sudo systemctl daemon-reload sudo systemctl start gunicorn sudo systemctl enable gunicorn sudo nginx -t sudo systemctl reload nginx部署过程中最容易出的问题就是静态文件404,你会发现后台管理页面样式全丢了。八成是Nginx静态文件路径配错了,或者collectstatic没跑。排查思路很简单:先在服务器上curl一下静态文件URL看有没有返回内容,没有就是Nginx路径问题,有就是浏览器缓存。
4.3 数据备份与安全加固
数据备份是农场系统的生命线,农事记录一旦丢了,等于一年的账本没了。我的备份策略是每天凌晨3点做一次数据库dump,保留最近30天的备份文件。
30 3 * * * pg_dump farm_db > /backup/farm_$(date +\%Y\%m\%d).sql在服务器上配置cron定期执行上面这条命令,文件按日期命名,管理起来一目了然。恢复时执行psql farm_db < 备份文件名即可。这里要特别强调,这个备份方案只能保证“数据库内容不丢”,如果你想连代码、配置文件、上传的图片也一起备份,可以先把整个项目目录打tar包,再打包上传到对象存储。
安全加固方面,我做了四件事。一是Django的SECRET_KEY不要硬编码在settings.py里,从环境变量读取,避免代码泄露导致session篡改风险。二是Nginx层强制HTTPS跳转并配置HTTP/2,农场的登录请求走明文太危险。三是数据库连接用专用账号,不用root,而且只允许本地访问。四是Admin后台的URL从默认的/admin/改成了一个自定义复杂路径,可以降低被扫描工具直接命中管理入口的风险。
如果说还有一个最容易被忽略的点,就是日志。生产环境一定要配置Django的LOGGING,把错误堆栈写到文件里。Gunicorn本身也会记录访问日志,出错时查看这两处日志能快速定位问题。我在实际运维中遇到过一次诡异的内存占用持续增长,最后是通过Gunicorn的日志发现是请求里带的二进制大数据导致worker内存膨胀,加了限制就好了。没有日志,这种问题几乎无法排查。
5. 常见问题排查与避坑速查
5.1 高频问题速查表
我把这套系统实际运行中遇到最多的问题整理成了一张表,每个问题都是真实场景里发生过的,照着排查能帮你省好几个小时。
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 页面样式全部丢失 | DEBUG=False但未collectstatic,或Nginx静态路径错误 | 浏览器F12看CSS请求是否404 | 执行collectstatic;检查Nginx的location配置 |
| 表单提交时报CSRF验证失败 | 模板中缺少csrf_token标签 | 查看页面源码中是否有csrfmiddlewaretoken字段 | 在form标签内加{% csrf_token %} |
| 上传图片后访问URL返回404 | settings中MEDIA_ROOT/URL配置错误,或开发环境未加static路由 | 检查MEDIA配置和urls.py | 补全配置和静态路由 |
| 迁移文件冲突,migrate报错 | 同一模型被多次makemigrations覆盖 | 查看迁移文件依赖关系 | 备份数据后按迁移文件顺序逐步执行 |
| 农事记录按日期统计数量翻倍 | JOIN关联后GROUP BY粒度不对 | 打印查询集SQL检查重复记录 | 使用子查询或去重方式统计 |
| 登录后跳转失败死循环 | LOGIN_REDIRECT_URL配置指向需要登录的页面 | 检查settings和视图装饰器 | 配置为登录后可直接访问的页面 |
| 管理后台无法导出Excel | django-import-export库未安装或未注册 | pip list检查包是否安装 | 安装并注册到INSTALLED_APPS |
| 任务列表页加载很慢 | 全表查询,未分页或未加日期筛选 | 查看Django Debug Toolbar的SQL执行时间 | 启用分页、日期筛选,必要时加索引 |
这张表里我想单独展开讲一下CSRF报错那个问题。新手经常遇到表单提交被拒,这是因为Django默认开启了CSRF防护,模板里的form标签内必须显式加上{% csrf_token %},而且这个标签要放在form标签内部,放在外面是不生效的。如果你用AJAX提交,还需要从cookie里读取csrftoken并加到请求头。这个机制容易让人烦躁,但它确实能拦截跨站请求伪造攻击,生产环境不要图省事关掉它。
5.2 从这套项目里总结的实战心得
做了这套农场上线系统以后,我对这类业务管理系统的开发有多层新认识,写出来分享给大家。
第一条心得是关于代码文档的。很多人写完代码不愿意写文档,尤其是部署文档,觉得“这有什么好写的,照着命令敲一遍就行”。但这套系统上线三个月后,新来的维护人员对着部署文档,40分钟就把环境从零搭好了;要是没有文档,光环境问题就能折腾两三个小时。所以无论你是课程设计答辩,还是商业项目交付,README和部署文档一定要写细,把每一步命令、每一个坑、每一个配置文件的作用都写清楚。这不算浪费时间,是项目价值的一部分。
第二条心得是关于需求边界。农场管理系统这种偏垂直的业务,需求会不断冒出来:你做了农事记录,农户就会问能不能顺便管农机;你做了库存,他们就问能不能关联采购订单。每个需求听起来都有道理,但全部做进去,项目就失控了。我的经验是,用“核心价值流”卡需求——地块、作物、农事、投入品、人员这条主链路是核心,其他功能想加入必须有明确的数据价值和管理价值,不然就先记录需求排到下个版本。最终这个农场系统做到了功能适度、结构清爽,核心使用率很高,反而是很多功能堆砌的系统上线后没人用。
第三条心得关于备份。农场管理系统上线后,我最不放心的不是功能bug,而是数据意外删除。农事记录这种东西一旦丢了不可恢复,而农场里的电脑和网络环境稳定性又远不如机房。所以我不仅在服务器上做自动备份,还要求管理员每周手动导出一份Excel存到不同电脑上。手动备份听起来原始,但它是容灾的最后一道防线。
最后提一句前端交互的细节。系统里有个值班日历,显示每天有谁在哪块地做了什么任务,我用了一周多时间来来回回调样式,因为农场工人主要用平板操作,屏幕不大、阳光底下看不清,所以界面元素要大、对比度要高,字号小于14px在实地场景里基本看不清。做一个管理系统不只是把字段堆上去,而是要把真实使用场景考虑进去,这种细节才是专业和业余的分水岭。
这套系统做完,我最大的体会是,技术选型不需要新潮,稳定可靠、团队熟悉、生态成熟就是好方案。如果你正好要做一个类似的管理系统,希望这篇分享能帮你少走点弯路。