ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Flask + Vue + MySQL 进销存系统设计与实战:从数据库到部署全攻略

Flask + Vue + MySQL 进销存系统设计与实战:从数据库到部署全攻略 1. 系统整体设计与技术选型1.1 为什么选 Flask Vue 这个组合我现在手头维护过十几个用 Python 写的业务系统如果要给“中小型进销存管理系统”这类项目做一个最稳妥的默认组合我大概率还是会选 Flask Vue MySQL。这不是说这个技术栈最“潮流”而是它在开发效率、学习门槛、部署复杂度三者之间拿到了一个很实际的平衡点。先说 Flask。相比 FastAPIFlask 的生态更老、沉淀更厚网上随便搜都能找到完整的踩坑记录这对做业务系统来说很重要。进销存系统本质上是个 CRUD 密集型的应用——商品管理、供应商管理、入库单、销售单、库存台账这些功能没有特别复杂的异步或者流式处理需求用 Flask 的同步请求模型完全够用。Flask-SQLAlchemy 做 ORM 映射、Flask-JWT-Extended 做登录鉴权、Flask-CORS 处理跨域这几个库配合起来非常顺手。Vue 这边我用的是 Vue 3 Vite Pinia Vue Router 这套组合。选 Vue 而不是 React主要是考虑到进销存这类后台管理系统的开发模式非常固定——左侧菜单、顶部面包屑、中间内容区表格加表单的 CRUD 页面占了绝大多数。Vue 的模板语法和双向绑定在这种场景下写起来最省事而且 Element Plus 组件库简直就是为后台管理系统量身定做的表格、分页、对话框、表单校验这些需求直接拿来用。MySQL 没什么好纠结的业务数据是强事务、强一致性的场景入库出库必须保证库存量准确MySQL 的 InnoDB 引擎和事务机制在这里就是正确答案。1.2 模块规划进销存系统到底拆成几块进销存系统听起来功能很多拆开看其实就五大块基础资料、采购进货、销售出库、库存管理、系统管理。我一开始做系统设计的时候没有急着写代码而是先按业务角色把功能模块画了一遍这步很关键。基础资料管理包含商品分类、商品档案、供应商档案、客户档案。商品档案是核心需要考虑条码、规格、单位、进价、售价、预警库存这些字段。采购进货模块处理进货入库采购单创建之后审核审核通过后自动增加库存并生成库存流水。销售出库模块反过来销售单创建、审核审核后扣减库存同时记录销售流水。库存管理模块包括库存查询、库存流水、库存预警还有一个比较重要的功能——盘点盘点差异要能生成盘盈盘亏单并调整库存。系统管理就是用户管理、角色权限、操作日志。系统给我的直观感受是进销存项目的难点不在单个功能难写而在于模块之间数据状态的一致性。采购单审核之后库存要变销售单退货之后库存要回调这些业务规则如果不在设计阶段想清楚后面联调的时候会疯狂返工。2. 数据库设计进销存的命根子2.1 核心表结构设计与外键关系数据库表设计是进销存系统里最不能省功夫的环节。我见过太多半路翻车的项目大多是表设计时字段命名混乱、外键关系没理清、金额字段用了 Float最后对账对不上。这里把核心表结构整理出来直接可以抄作业。第一张表是用户表sys_user字段包括 id、username、password存哈希值、real_name、role_id、status。密码必须加密存储哪怕系统是内网使用的也不能明文存密码用 Werkzeug 自带的密码哈希函数就行。第二张核心表是商品表product设计的时候要注意几个关键字段category_id外键关联商品分类表product_code是商品编码或条码product_name商品名称specification规格型号unit计量单位purchase_price进货价sale_price零售价stock_quantity当前库存warning_stock库存预警线。这里有个容易踩坑的地方stock_quantity是冗余字段它应该等于所有入库流水之和减去所有出库流水之和但不能每次都实时计算所以要存量字段。这个字段必须配合库存流水表做幂等更新。第三块是流水类表包括stock_flow库存流水表和business_order业务单据主表。库存流水表记录每一笔库存变动字段包括 product_id、change_type进货入库/销售出库/退货入库/盘盈/盘亏、change_quantity正数入库、负数出库、before_stock、after_stock、related_order_no、create_time、create_by。这个表设计好了后面做库存追溯、财务对账就有了依据。下面贴出核心建表 SQL精简版-- 商品分类表 CREATE TABLE category ( id int NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 分类名称, parent_id int DEFAULT 0 COMMENT 父级分类ID, sort_order int DEFAULT 0 COMMENT 排序, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品分类表; -- 商品表 CREATE TABLE product ( id int NOT NULL AUTO_INCREMENT, category_id int NOT NULL COMMENT 分类ID, product_code varchar(50) NOT NULL COMMENT 商品编码, product_name varchar(100) NOT NULL COMMENT 商品名称, specification varchar(100) DEFAULT NULL COMMENT 规格, unit varchar(20) DEFAULT NULL COMMENT 单位, purchase_price decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 进货价, sale_price decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 销售价, stock_quantity int NOT NULL DEFAULT 0 COMMENT 当前库存, warning_stock int DEFAULT 10 COMMENT 预警库存, status tinyint DEFAULT 1 COMMENT 状态 1启用 0停用, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_product_code (product_code), KEY idx_category_id (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表; -- 库存流水表 CREATE TABLE stock_flow ( id int NOT NULL AUTO_INCREMENT, product_id int NOT NULL, change_type varchar(20) NOT NULL COMMENT 类型: PURCHASE_IN/SALE_OUT/RETURN_IN/CHECK_IN/CHECK_OUT, change_quantity int NOT NULL COMMENT 变动数量入库为正出库为负, before_stock int NOT NULL COMMENT 变动前库存, after_stock int NOT NULL COMMENT 变动后库存, business_no varchar(50) NOT NULL COMMENT 业务单号, remark varchar(200) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, create_by varchar(50) DEFAULT NULL, PRIMARY KEY (id), KEY idx_product_id (product_id), KEY idx_business_no (business_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT库存流水表;2.2 金额字段设计经验Decimal 是底线我强烈建议金额字段一律使用decimal(10,2)。有的同学刚上手时图省事用 Float 存金额后面做统计报表时就会感受到什么叫“浮点数陷阱”——0.1 0.2 不等于 0.3 这件事在数据库中同样存在对账对不平排查起来非常痛苦。表字段的类型选择我个人习惯遵循这个原则状态类的用 tinyint数量类的用 int金额类的用 decimal时间类的用 datetime带时区要求的用 timestamp。字符串统一 utf8mb4排序规则用 utf8mb4_general_ci这个排序规则对中文支持友好查询效率也高。外键要不要在数据库层面强制建立这个业内一直有争议。我的经验是在开发阶段不加物理外键而是通过代码层的逻辑约束来保证数据一致性。原因有二一是物理外键会让批量导入数据、删除数据变得很繁琐二是业务系统经常有“逻辑删除”的需求物理外键在逻辑删除场景下会产生额外的麻烦。所以表设计上只保留逻辑关联字段和索引外键约束在实际开发中靠事务和代码保证。3. 后端 Flask 核心 API 实现3.1 项目初始化与蓝图模块化Flask 项目如果所有路由都写在 app.py 里代码很快就会膨胀到没法维护。进销存业务涉及的接口数量少说也有六十个以上我一开始就用Blueprint 蓝图做了模块化拆分。项目结构大致这样supermarket_erp/ ├── app.py # 入口文件创建 app 对象 ├── config.py # 配置文件数据库连接、JWT 密钥等 ├── requirements.txt # 依赖清单 ├── models/ # ORM 模型 │ ├── __init__.py │ ├── user.py │ ├── product.py │ ├── category.py │ ├── stock_flow.py │ └── order.py ├── api/ # 蓝图路由 │ ├── __init__.py │ ├── auth.py # 登录、用户管理 │ ├── product_api.py # 商品相关接口 │ ├── category_api.py # 分类相关接口 │ ├── stock_api.py # 库存相关接口 │ └── order_api.py # 采购和销售单据接口 ├── utils/ # 通用工具函数 │ ├── __init__.py │ ├── response.py # 统一响应格式 │ └── decorators.py # 权限校验装饰器 └── logs/ # 日志目录在app.py里注册蓝图才是关键的部分from flask import Flask from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy from flask_jwt_extended import JWTManager from config import Config db SQLAlchemy() jwt JWTManager() def create_app(): app Flask(__name__) app.config.from_object(Config) db.init_app(app) jwt.init_app(app) CORS(app, resources{r/api/*: {origins: *}}, supports_credentialsTrue) # 注册蓝图 from api.auth import auth_bp from api.product_api import product_bp from api.category_api import category_bp from api.stock_api import stock_bp from api.order_api import order_bp app.register_blueprint(auth_bp, url_prefix/api/auth) app.register_blueprint(product_bp, url_prefix/api/product) app.register_blueprint(category_bp, url_prefix/api/category) app.register_blueprint(stock_bp, url_prefix/api/stock) app.register_blueprint(order_bp, url_prefix/api/order) return app if __name__ __main__: app create_app() app.run(host0.0.0.0, port5000, debugTrue)清晰的路由前缀对应清晰的模块边界前端联调时看接口路径就知道是哪个模块的接口排查问题效率能提升很多。CORS 配置要特别注意开发时可以先放开但生产环境一定要限定具体的跨域来源否则会有安全风险。3.2 JWT 登录鉴权与统一响应封装进销存系统是多人使用的有管理员、采购员、销售员、仓库管理员这些角色登录鉴权和权限控制是必须的。我这里用flask-jwt-extended做 Token 鉴权。登录接口校验用户名密码校验通过后签发 JWT Token后续所有业务接口在请求头带Authorization: Bearer token即可。用户密码校验用的是 Werkzeug 的check_password_hash。用户登录成功后在 JWT 的额外 claims 里塞入用户 ID 和角色 ID之后每个受保护接口都能通过get_jwt_identity()拿到当前用户身份做权限判断from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity auth_bp.route(/login, methods[POST]) def login(): data request.get_json() username data.get(username) password data.get(password) user User.query.filter_by(usernameusername).first() if not user or not check_password_hash(user.password, password): return jsonify({code: 40001, message: 用户名或密码错误}), 200 if user.status ! 1: return jsonify({code: 40002, message: 账号已被禁用}), 200 access_token create_access_token( identityuser.id, additional_claims{role_id: user.role_id, username: user.username} ) return jsonify({code: 20000, message: 登录成功, data: {token: access_token, username: user.username}})统一响应格式是我个人很坚持的一点。后端接口不管成功失败返回 JSON 都遵循{code, message, data}这个结构。code 为 20000 表示成功其他码表示业务异常。这样前端可以用 axios 拦截器统一处理响应状态不用每个页面重复判断。权限这块做一个简单的装饰器就够了def role_required(*roles): def decorator(fn): wraps(fn) jwt_required() def wrapper(*args, **kwargs): claims get_jwt() if claims.get(role_id) not in roles: return jsonify({code: 40300, message: 无权限访问}), 200 return fn(*args, **kwargs) return wrapper return decorator3.3 核心业务接口采购入库与销售出库怎么保证库存准确进销存系统最核心的逻辑就是单据审核后变更库存同时记录库存流水。这块我讲细一点。采购入库接口的逻辑前端提交采购单包含商品 ID、进货数量、进货单价后端先在一个事务里做三件事——查询商品当前库存、计算新的库存量、更新商品库存然后插入库存流水记录最后记录采购订单。这三步必须在一个事务里任何一步失败都要回滚否则就会出现库里库存和流水对不上的情况。order_bp.route(/purchase/in, methods[POST]) jwt_required() def purchase_in(): data request.get_json() items data.get(items, []) if not items: return jsonify({code: 40010, message: 进货商品列表不能为空}), 200 business_no generate_business_no(PO) try: for item in items: product Product.query.filter_by(iditem[product_id]).with_for_update().first() if not product: raise Exception(f商品ID {item[product_id]} 不存在) quantity int(item[quantity]) if quantity 0: raise Exception(进货数量必须大于0) before_stock product.stock_quantity after_stock before_stock quantity product.stock_quantity after_stock flow StockFlow( product_idproduct.id, change_typePURCHASE_IN, change_quantityquantity, before_stockbefore_stock, after_stockafter_stock, business_nobusiness_no, remarkdata.get(remark, ) ) db.session.add(flow) db.session.commit() return jsonify({code: 20000, message: 进货入库成功, data: {business_no: business_no}}) except Exception as e: db.session.rollback() return jsonify({code: 50000, message: str(e)}), 200注意这里使用了with_for_update()也就是 MySQL 的行级锁。多用户同时操作同一个商品时不加锁就会出现超卖或库存变负的问题。虽然进销存系统的并发量通常不高但这一点我在实际项目里吃过亏所以必须强调。销售出库接口的逻辑相反校验库存足够后扣减库存出库流水数量为负值。退货入库则重新加回库存并记录退货原因。整个系统的库存台账就是这样通过流水表完整保留下来想要知道任何时点库存变化查流水表一目了然。4. 前端 Vue 页面体系与接口对接4.1 Vue 3 Vite 环境搭建与项目结构前端部分我用的是 Vue 3 Vite。相比 Vue CLIVite 的启动速度和构建速度都有质的提升热更新也跟手得多。前台开发时改一行代码浏览器秒级刷新这种体验对开发效率的影响真的很大。创建项目直接跑npm create vuelatest这个命令会引导你选择需要的特性我一般勾选 Vue Router、Pinia、ESLint 这三项。项目创建完再装 Element Plus 和 Axiosnpm install element-plus npm install axios前端项目的目录结构我也保持模块化src/ ├── api/ # 接口请求封装 │ ├── request.js # axios 实例 │ ├── auth.js │ ├── product.js │ └── order.js ├── router/ # 路由配置 │ └── index.js ├── store/ # Pinia 状态管理 │ └── user.js ├── views/ # 页面组件 │ ├── Login.vue │ ├── Dashboard.vue │ ├── product/ProductList.vue │ ├── product/ProductEdit.vue │ ├── stock/StockList.vue │ ├── stock/StockFlow.vue │ └── order/PurchaseOrder.vue └── layout/ └── MainLayout.vue # 后台主框架4.2 关键页面实现商品管理与库存查询商品管理页是标准的表格 CRUD 页面。左侧放分类树右侧放商品列表。分类树用 Element Plus 的el-tree组件商品列表用el-table工具栏放搜索框、新增、导入导出按钮。分页用el-pagination每页默认 20 条。我直接说几个实现时容易踩的细节一是搜索条件要带防抖。用户在搜索框输入商品名称时不应该每敲一个字就发一次请求至少要做 300ms 的防抖输入结束再请求接口。不然写一半页面上会连续打出好几个待选建议列表。二是表格列渲染要格式化。金额字段在el-table-column里可以用:formatter方法保留两位小数库存字段低于预警线时给该行添加高亮 class这样预警效果一眼可见比看数字判断直观多了。三是编辑弹窗的表单校验。原价降不降不重要但必填字段、数字范围这些校验必须做。Element Plus 的el-form配 rules 校验规则在el-form-item的 prop 上绑定字段名提交时validate()一下不通过就不发请求。库存流水页面就简单一些顶部按商品、日期范围过滤中间一个表格按时间倒序展示流水记录列包括时间、业务类型、变动数量、变动前后库存、业务单号、操作人。这个页面配合商品库存列表基本能回答老板大部分“这个东西到底还有多少、进出记录怎么样”的问题。4.3 动态路由与 Pinia 状态管理进销存系统涉及多个操作角色不同角色的菜单权限不同。我这里前端做了动态路由——登录成功后根据当前用户角色去拿菜单权限列表再动态往路由表里加路由。Pinia 状态管理主要放在用户信息、菜单权限、全局公共数据这三类。用户信息在登录成功后就存起来菜单权限在路由守卫里获取。主导航栏和侧边栏根据 store 里的菜单数据渲染好处是切换账号时不用刷新整个页面菜单会自动跟着变了。// store/user.js import { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ token: localStorage.getItem(token) || , username: , roleId: null, menus: [] }), actions: { setLoginInfo(data) { this.token data.token this.username data.username this.roleId data.role_id localStorage.setItem(token, data.token) }, logout() { this.token this.username this.roleId null this.menus [] localStorage.removeItem(token) } } })路由守卫的逻辑也很直观router.beforeEach((to, from, next) { const userStore useUserStore() if (to.path ! /login !userStore.token) { next(/login) } else { next() } })4.4 axios 拦截器封装Token 注入与统一错误处理前端请求封装最关键的是 axios 拦截器。一个是请求拦截器每次请求自动把 Token 加到请求头一个是响应拦截器统一处理后端返回的 code、处理 401 Token 过期自动跳登录页。// api/request.js import axios from axios import { ElMessage } from element-plus import { useUserStore } from /store/user import router from /router const request axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || /api, timeout: 15000 }) request.interceptors.request.use(config { const userStore useUserStore() if (userStore.token) { config.headers[Authorization] Bearer ${userStore.token} } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 20000) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { const userStore useUserStore() userStore.logout() router.push(/login) } ElMessage.error(网络请求异常请稍后重试) return Promise.reject(error) } ) export default request这段代码是后台系统前端的骨架所有页面走这个封装的请求实例全局的登录失效处理、错误提示自动就带上了不用每个页面重复写。5. 前后端联调与生产部署5.1 手机端/局域网访问与 Vue 代理配置开发模式下前端跑在 5173 端口后端跑在 5000 端口两者互相独立。如果直接让前端页面请求http://localhost:5000/api/xxx会产生跨域问题。解决方案有两种一是后端开 CORS二是在 Vite 里配置代理。开发环境我更推荐用 Vite 代理前端代码里所有请求都写相对路径/api由 Vite 开发服务器把请求转发给后端。这样后端也省事前端代码部署到生产环境后也不用改任何接口地址。Vite 配置如下// vite.config.js export default defineConfig({ plugins: [vue()], server: { port: 5173, host: true, proxy: { /api: { target: http://localhost:5000, changeOrigin: true } } } })注意这里的host: true很关键。如果只是本地自己开发调试不加也行但如果你像做员工培训、演示需要局域网内手机或同事电脑访问你电脑上的页面这个配置就必须打开。5.2 Nginx 部署配置与 MySQL 定时备份生产环境部署的话前端打包后是纯粹的静态文件可以直接丢给 Nginx。前端记住一件事生产环境同样不希望换接口地址所以 Nginx 要配一个反向代理把/api开头的请求转发给 Flask 后端服务。一个基础但实用的 Nginx 配置是这样server { listen 80; server_name your_domain_or_ip; # 前端静态文件 root /var/www/supermarket_erp/dist; index index.html; # 前端路由 history 模式必需 location / { try_files $uri $uri/ /index.html; } # 后端 API 反向代理 location /api/ { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里最容易漏掉的是try_files $uri $uri/ /index.html;这一行。Vue Router 用的 history 模式如果不配置这行用户点击页面内跳转没问题但刷新或直接输入某个子页面 URL就会出现 404。MySQL 定时备份也是上线第一件事。建议写一个简单的脚本每天凌晨通过 crontab 跑任务导出 SQL保留最近 7 天#!/bin/bash BACKUP_DIR/data/backup/mysql DATE$(date %Y%m%d) mysqldump -u erp_user -p密码 supermarket_erp $BACKUP_DIR/erp_$DATE.sql find $BACKUP_DIR -mtime 7 -name *.sql -exec rm -f {} \;生产环境备份这件事我有一次因为磁盘满了没注意备份任务连续失败一周后来数据库被误操作删表了才发现手里只有一周前的数据损失非常大。从那以后我上线任何系统第一件事永远是确认备份脚本在跑。5.3 局域网部署的注意事项如果只是超市内部使用不暴露到公网其实部署很简单一台普通电脑装 MySQL、Python 环境、Nginx前端打包后的dist目录放到指定位置就行。员工浏览器访问的地址就是后端机器在局域网里的 IP。我个人会额外注意两点一是 MySQL 的配置优化。进销存系统加国产报表查询把max_connections调大一些innodb_buffer_pool_size根据机器内存调默认的 128M 在数据量上来之后查询会明显变慢。二是防火墙放行端口。后端跑 5000 端口前端 80 端口有时候系统启动了一切正常但局域网其他机器就是访问不了大概率是防火墙没放行。自己电脑直接访问localhost当然没问题一换 IP 访问就卡住。很多新手在这里浪费时间。6. 实操中遇到的典型问题与解决记录6.1 并发扣库存导致数据不一致这个问题我之前在开发环境里几乎测不出来因为一直是单用户操作。后来让店里两个收银员同时卖同一个商品对账发现库存少了。排查下来是销售出库接口没有加行锁两台收银电脑同时提交销售单后一个请求读到的库存还是旧的覆盖了前一个请求的结果。解决办法就是上面说的with_for_update()行级锁。加锁后同一时刻只有一个事务能修改该商品的库存另一个事务会等待锁释放后再读最新值。这个坑非常隐蔽如果不在代码层面处理上线后会在某个偶然时刻让你对账对到抓狂。6.2 时间字段混乱导致统计报表偏差开发阶段我用的是datetime字段存进去之后查出来看着都正常。后来发现有些接口查询今天的销售单总和总数对不上。查来查去发现是时区问题Python 的datetime.now()默认取的是本地时间MySQL 默认用系统的时区服务器时间如果不是中国标准时间存进去的数据就会偏移。解决方案是统一在config.py里设置app.config[SQLALCHEMY_TRACK_MODIFICATIONS] False app.config[SQLALCHEMY_ENGINE_OPTIONS] { pool_size: 10, pool_recycle: 3600, connect_args: {} }同时 MySQL 连接字符串 URL 里加上?charsetutf8mb4时间字段统一在 SQLAlchemy 模型里用datetime类型程序内部全部用datetime.now()生成这样保证所有写入的时间用的都是后端进程的时区统计报表才不会有偏差。6.3 前端页面表格大数据量渲染卡顿商品数量上万后在el-table里一次性渲染全部数据页面明显卡顿滚动都抖。这个问题主要发生在盘点库存和商品列表页面。解决办法是后端接口强制分页前端表格配合分页组件每页 20 条切页时才请求数据。如果商品数量很大同时还要搜索过滤可以考虑在数据库层面做 LIKE 查询然后分页。前端不要一次性把全量数据 load 到内存里再自己 filter。这条经验不值钱但真的能省下很多性能排查的时间。6.4 接口响应慢与 N1 查询问题开发完商品列表接口时测了一下性能居然要 2 秒多才返回。一看日志问题出在 SQLAlchemy 的关联查询上。商品表格里显示分类名称最自然的写法是在循环里查分类表结果几百条商品数据一个接口发了几百条 SQL 查询。这就是典型的 N1 查询问题。解决办法是用joinedload或者subqueryload一次性把关联数据查出来products Product.query.options(db.joinedload(Product.category)).paginate(...)这样 SQL 查询次数从 N1 变成 11接口耗时从 2 秒降到 200 毫秒以内。接口开发过程中一个重要的习惯是观察 SQLAlchemy 的查询日志看到大量重复 SQL 就要意识到关联查询没优化。7. 实际操作中的补充经验与后续扩展方向最后想聊一点个人实际操作中的体会供后面接手你项目的同事参考。第一进销存这类系统的后端接口宁可多写几个小的专用接口也不要为了省事写一个万能接口。比如商品列表和商品下拉选项虽然返回的都是商品数据但一个需要分页过滤一个只需要简单的 id 和名称接口连返回字段都不一样。拆开写前端代码会更清晰后端也不用每次都做无谓的查询。第二权限模型的复杂度要匹配实际使用场景。我见过很多进销存项目一开始就把权限表设计得非常复杂菜单权限、按钮权限、数据权限三层结果实际使用的时候店里只有老板和一个收银员两个账号复杂权限模型根本用不上。设计权限系统前先问清楚实际会有多少角色、角色差异是什么够用就好。这个系统后续还可以扩展的方向我个人建议优先考虑这几个多门店支持把门店维度加进商品、单据、库存里、供应商对账和客户账期管理进销存加了应收应付就是半个财务系统、基于销售数据的补货建议结合预警库存和近 30 天销售速度自动生成采购建议单。每一步扩展都建立在现有基础数据和流水表结构之上前提是表设计阶段没有偷懒把冗余字段和流水台账都做扎实。用 Flask Vue 做这类系统代码量不大整个链路也不复杂真正决定系统好不好用的从来不是框架本身而是业务逻辑是否严谨、数据结构是否考虑周全。照着上面这套结构和实操经验走再做不好不太可能祝你顺利。
返回列表