news 2026/10/17 21:49:09

汽车配件管理系统源代码解析:供应链与仓储模块落地实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
汽车配件管理系统源代码解析:供应链与仓储模块落地实践

简介:这是一套面向物流与供应链管理行业的汽车配件管理系统源代码,适合Java企业级开发学习者、供应链信息化从业者及毕业设计参考者使用。系统围绕整车运输、仓储管理、货运代理、售后备件与金龙DK包装出口五大业务场景展开,涵盖订单处理、路线规划、车辆调度、实时跟踪、入库存储、库存监控、拣选打包、报关保险、定制包装与出口文档准备等完整功能链路。技术栈以Spring Boot为主,融合SSM与SSH两种经典企业级架构优势,并预留微服务与Docker容器化的扩展思路。资源包共2016个文件,约311.82MB,以866个js、409个html、139个css构成前端页面与交互,106个java、354个xml、12个sql承载后端逻辑与数据层,另有json、properties等配置及md、doc说明文档,目录结构完整。已有59人学习,适合借此理解供应链系统的模块划分、前后端协作与架构选型落地方式。

1. 汽车配件管理系统源代码:从供应链到仓储的落地拆解

很多做汽配生意的朋友都有个共同的痛点:仓库里几万个 SKU,同一个配件可能有三种叫法,采购单和销售单对不上,月底盘点永远差那么几件。市面上成熟的 ERP 要么贵得离谱,要么功能冗余到根本用不上。这份汽车配件管理系统源代码,就是冲着这个场景来的——它把供应链和仓储两个核心环节做成了可跑通的模块,适合中小型汽配贸易商、维修连锁店的技术负责人,或者想拿一套真实业务系统练手的开发者。代码结构不算复杂,但业务逻辑覆盖得比较完整,从配件入库、库存预警到采购建议都有对应实现。下面我按实际拆包和部署的顺序,把这份源码里真正值得看的部分讲清楚。

2. 技术栈选型与数据库设计:为什么这么搭

2.1 后端框架与前端方案的取舍

拿到源码先看技术栈,这决定了你后续维护的成本。这份系统后端用的是 Python 的 Flask 框架,前端是 Bootstrap + jQuery 的组合。有人可能会问,为什么不用 Django 或者 Vue?我的判断是,汽配管理系统的核心复杂度在业务逻辑和数据库查询上,不在前端交互。Flask 足够轻,路由和蓝图机制清晰,二次开发时改起来不费劲。前端用 Bootstrap 是因为配件录入、库存查询这些页面本质上是表单和表格的堆叠,不需要复杂的组件状态管理。

数据库选的是 MySQL 5.7 以上版本,字符集要求 utf8mb4。这一点很关键,因为配件名称里经常出现特殊符号,比如“φ”“°”这类工程标注,utf8 存不下会直接报错。源码里config.py的数据库连接串已经写好了,你只需要改成本地环境的信息。

# config.py 数据库配置片段 SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:password@localhost:3306/auto_parts?charset=utf8mb4' SQLALCHEMY_TRACK_MODIFICATIONS = False

逻辑说明:这里用的是 PyMySQL 驱动,不是默认的 MySQLdb。如果你本地没装 PyMySQL,pip install pymysql补上就行。charset=utf8mb4必须显式指定,否则建表时默认走 latin1,后面存中文配件名会乱码。参数TRACK_MODIFICATIONS关掉是为了省内存,开发环境无所谓,生产环境建议保持 False。

2.2 核心表结构与字段含义

供应链和仓储的联动全靠表结构撑起来。源码里建表 SQL 放在migrations/目录下,我挑三张最核心的表说。

表名用途关键字段注意点
parts配件主数据part_no、part_name、spec、unitpart_no建唯一索引,防止重复录入
inventory实时库存part_id、warehouse_id、quantity、safe_stockquantity和safe_stock用 DECIMAL 不用 INT
purchase_order采购单order_no、supplier_id、status、total_amountstatus用枚举值控制流转

inventory表里quantity字段用 DECIMAL(10,2) 而不是 INT,这是血泪经验。汽配里有些物料按米、升、公斤卖,比如刹车油、线束,整数存不下。safe_stock是安全库存阈值,低于这个值系统会触发预警,后面库存预警模块会用到。

建表时还有一个容易翻车的地方:外键约束。源码里inventory.part_id关联parts.id,但没加ON DELETE CASCADE。这意味着你删配件主数据时,如果库存表里有记录,数据库会直接拒绝。这是故意的,防止误删导致库存记录变成孤儿数据。要删配件,得先把库存清零或者手动处理关联记录。

2.3 初始化数据与依赖安装

源码包里带了一个seed_data.py脚本,用来灌入基础数据,包括仓库信息、供应商分类、配件单位等。跑之前先确认依赖装全了。

# 创建虚拟环境并安装依赖 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt # 初始化数据库表结构 flask db upgrade # 灌入基础数据 python seed_data.py

逻辑说明:flask db upgrade走的是 Flask-Migrate,它会读取migrations/下的版本文件逐步建表。如果你改了模型字段,需要先flask db migrate -m "描述"生成迁移脚本,再 upgrade。seed_data.py里写的是幂等插入,重复跑不会产生重复数据,但会跳过已存在的记录。参数方面,requirements.txt里锁了 Flask 2.0.3 和 SQLAlchemy 1.4.46,版本别乱升,2.1 之后有些 API 变了会报错。

3. 供应链模块:采购单流转与供应商管理

3.1 采购单状态机与业务流转

供应链模块的核心是采购单。源码里把采购单做成了一个状态机,状态值定义在models/purchase.py里,流转路径是:草稿 → 待审核 → 已审核 → 部分到货 → 全部到货 → 已完成。每个状态变更都对应一个接口,不是随便改字段。

# models/purchase.py 状态流转核心逻辑 class PurchaseOrder(db.Model): STATUS_DRAFT = 'draft' STATUS_PENDING = 'pending' STATUS_APPROVED = 'approved' STATUS_PARTIAL = 'partial' STATUS_RECEIVED = 'received' STATUS_DONE = 'done' def approve(self, operator): if self.status != self.STATUS_PENDING: raise ValueError('只有待审核状态才能审核通过') self.status = self.STATUS_APPROVED self.approved_by = operator self.approved_at = datetime.utcnow()

逻辑说明:approve方法里先做状态校验,不是待审核状态直接抛异常。这是防止前端绕过流程直接调接口改状态。approved_by和approved_at记录操作人和时间,方便追溯。参数上,operator传的是当前登录用户的 ID,源码里从 session 里取,你接自己的权限系统时改这里就行。

采购单还有一个细节:部分到货的处理。当供应商只发了一部分货,状态变成partial,系统会自动生成一条待入库记录,剩余数量留在采购单上。下次到货时继续累加,直到received_quantity >= total_quantity才变成received。这个逻辑在services/purchase_service.py里,用的是数据库行锁with_for_update(),防止并发入库时数量算错。

3.2 供应商管理与采购建议生成

供应商表supplier里除了基本信息,还有一个rating字段,用来记录供应商评级。源码里没做自动评级,但留了接口。我一般会按到货准时率和退货率两个维度来算,公式是:rating = 准时到货单数 / 总单数 * 0.7 + (1 - 退货金额 / 总采购金额) * 0.3。这个你可以按自己业务调。

采购建议的生成逻辑在services/recommend_service.py。它查的是inventory表里quantity < safe_stock的配件,然后按供应商分组,算出建议采购量。建议量 = 安全库存 * 2 - 当前库存,这个系数 2 是拍脑袋定的,源码注释里写了“可根据实际周转率调整”。

# services/recommend_service.py 采购建议核心查询 def generate_purchase_suggestion(): low_stock_items = db.session.query(Inventory).filter( Inventory.quantity < Inventory.safe_stock ).all() suggestions = [] for item in low_stock_items: suggest_qty = item.safe_stock * 2 - item.quantity suggestions.append({ 'part_no': item.part.part_no, 'part_name': item.part.part_name, 'current_qty': float(item.quantity), 'suggest_qty': float(suggest_qty), 'supplier': item.part.default_supplier.name if item.part.default_supplier else '未指定' }) return suggestions

逻辑说明:查询条件quantity < safe_stock是触发预警的阈值。suggest_qty的计算里,safe_stock * 2是目标库存水位,减去当前库存就是建议采购量。如果配件没设默认供应商,返回“未指定”,前端会标红提示。参数上,safe_stock在配件录入时设置,建议按历史日均销量的 1.5 倍来填,太低了频繁触发预警,太高了占资金。

3.3 采购入库与库存联动

采购单审核通过后,仓库人员做入库操作。入库接口在views/inventory.py,核心是往inventory表里加数量,同时写一条inventory_log流水。这里有个坑:入库数量不能超过采购单剩余未入库数量,源码里做了校验,但校验逻辑在 service 层,不在数据库约束里。如果你直接改数据库,这个校验就绕过去了。

# views/inventory.py 入库接口片段 @inventory_bp.route('/inbound', methods=['POST']) def inbound(): data = request.get_json() order_id = data.get('order_id') items = data.get('items') # [{'part_id': 1, 'qty': 10}, ...] order = PurchaseOrder.query.with_for_update().get(order_id) if order.status not in [PurchaseOrder.STATUS_APPROVED, PurchaseOrder.STATUS_PARTIAL]: return jsonify({'code': 400, 'msg': '当前状态不允许入库'}), 400 for item in items: # 校验入库数量不超过剩余数量 remaining = order.get_remaining_qty(item['part_id']) if item['qty'] > remaining: return jsonify({'code': 400, 'msg': f'入库数量超过剩余量,剩余 {remaining}'}), 400 # 更新库存 inventory = Inventory.query.filter_by( part_id=item['part_id'], warehouse_id=data['warehouse_id'] ).with_for_update().first() inventory.quantity += item['qty'] # 写流水 log = InventoryLog(part_id=item['part_id'], change_qty=item['qty'], type='purchase_in', ref_order=order.order_no) db.session.add(log) order.update_receive_status() db.session.commit() return jsonify({'code': 200, 'msg': '入库成功'})

逻辑说明:with_for_update()是行锁,防止两个入库单同时改同一条库存记录导致数量覆盖。get_remaining_qty是采购单模型上的方法,算的是total_quantity - received_quantity。update_receive_status会根据所有明细的入库情况自动把采购单状态推到partial或received。参数上,warehouse_id从请求体里传,支持多仓库场景,如果只有一个仓库,前端写死也行。

4. 仓储模块:库存预警、盘点与调拨

4.1 库存预警的触发机制与通知方式

库存预警不是简单查一下低于安全库存就完事。源码里做了两级预警:黄色预警是quantity < safe_stock,红色预警是quantity < safe_stock * 0.5。预警数据通过一个定时任务生成,用的是 APScheduler,配置在tasks/scheduler.py。

# tasks/scheduler.py 预警任务 from apscheduler.schedulers.background import BackgroundScheduler scheduler = BackgroundScheduler() def check_stock_alert(): yellow_items = Inventory.query.filter( Inventory.quantity < Inventory.safe_stock, Inventory.quantity >= Inventory.safe_stock * 0.5 ).all() red_items = Inventory.query.filter( Inventory.quantity < Inventory.safe_stock * 0.5 ).all() # 写入 alert 表,前端轮询展示 for item in yellow_items: Alert.create_if_not_exists(item, level='yellow') for item in red_items: Alert.create_if_not_exists(item, level='red') scheduler.add_job(check_stock_alert, 'interval', minutes=30) scheduler.start()

逻辑说明:check_stock_alert每 30 分钟跑一次,分两档查。create_if_not_exists是幂等写入,同一条库存记录不会重复生成预警,除非上次预警被标记为已处理。参数上,minutes=30可以改,但别太短,频繁查库影响性能。通知方式源码里只写了写库,前端轮询。你要接邮件或企业微信,在create_if_not_exists后面加发送逻辑就行。

4.2 盘点单的生成与差异处理

盘点是仓储模块里最容易出问题的环节。源码里的盘点逻辑是:选仓库 → 生成盘点单 → 打印空白盘点表 → 人工填实际数量 → 录入系统 → 生成差异 → 审核调整。盘点单表stocktaking和明细表stocktaking_item是主从关系。

# services/stocktaking_service.py 生成盘点单 def create_stocktaking(warehouse_id, operator): # 快照当前库存 inventories = Inventory.query.filter_by(warehouse_id=warehouse_id).all() st = Stocktaking(warehouse_id=warehouse_id, status='draft', created_by=operator) db.session.add(st) db.session.flush() # 拿到 st.id for inv in inventories: item = StocktakingItem( stocktaking_id=st.id, part_id=inv.part_id, book_qty=inv.quantity, # 账面数量 actual_qty=None # 待录入 ) db.session.add(item) db.session.commit() return st

逻辑说明:book_qty是生成盘点单时的库存快照,后面录入actual_qty后算差异。flush()是为了拿到自增 ID,不然明细插不进去。参数上,warehouse_id必传,不支持全仓盘点,因为数据量大了页面会卡。差异处理在confirm_stocktaking方法里,差异数量会写一条inventory_log,类型是stocktaking_adjust,然后更新inventory.quantity为实际数量。

4.3 仓库调拨与在途库存

调拨是从一个仓库转到另一个仓库。源码里调拨单transfer_order有from_warehouse_id和to_warehouse_id,状态分draft、in_transit、completed。关键点在“在途库存”的处理:调拨单审核后,源仓库库存先扣减,目标仓库不立即增加,而是记在in_transit_qty字段上,等目标仓库确认收货后才加到实际库存。

# services/transfer_service.py 调拨审核 def approve_transfer(order_id): order = TransferOrder.query.with_for_update().get(order_id) if order.status != 'draft': raise ValueError('只有草稿状态才能审核') for item in order.items: # 扣减源仓库库存 from_inv = Inventory.query.filter_by( part_id=item.part_id, warehouse_id=order.from_warehouse_id ).with_for_update().first() if from_inv.quantity < item.qty: raise ValueError(f'配件 {item.part.part_no} 库存不足') from_inv.quantity -= item.qty # 增加在途库存 to_inv = Inventory.query.filter_by( part_id=item.part_id, warehouse_id=order.to_warehouse_id ).with_for_update().first() to_inv.in_transit_qty += item.qty order.status = 'in_transit' db.session.commit()

逻辑说明:源仓库扣减前先校验库存够不够,不够直接抛异常回滚。in_transit_qty是目标仓库库存表上的字段,表示“正在路上”的数量。目标仓库确认收货时,in_transit_qty减掉,quantity加上。参数上,调拨数量item.qty不能超过源仓库可用库存,可用库存 =quantity - locked_qty,源码里locked_qty暂时没用,预留字段。

5. 避坑与排查:部署和运行时的五个常见问题

5.1 数据库连接报错 “Access denied for user”

现象:跑flask db upgrade时直接报pymysql.err.OperationalError: (1045, "Access denied for user 'root'@'localhost'")。

原因:config.py里的数据库密码是占位符password,没改成你本地 MySQL 的真实密码。或者 MySQL 8.0 默认认证插件是caching_sha2_password,PyMySQL 老版本不支持。

解决:先把config.py里的密码改对。如果是 MySQL 8.0,要么升级 PyMySQL 到 1.0 以上,要么改用户认证方式:ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';。我一般直接升级驱动,改认证方式会影响其他工具连接。

5.2 配件名称保存后变成问号

现象:录入配件时中文名称正常,保存到数据库再查出来变成???。

原因:建库时没指定utf8mb4,或者连接串里漏了charset=utf8mb4。MySQL 默认字符集在 5.7 是 latin1,8.0 是 utf8mb4,但老版本升级上来的库可能还是 latin1。

解决:检查三个地方——数据库级别SHOW CREATE DATABASE auto_parts;、表级别SHOW CREATE TABLE parts;、连接串。任何一处不是 utf8mb4 都要改。改表用ALTER TABLE parts CONVERT TO CHARACTER SET utf8mb4;。注意,改之前备份数据,转换字符集可能截断超长字段。

5.3 采购单审核后状态没变

现象:前端点了审核按钮,提示成功,但刷新后采购单还是“待审核”。

原因:审核接口里db.session.commit()没执行,或者被前面的异常跳过了。源码里approve方法只改了对象属性,commit 在 view 层。如果 view 层在 commit 前有 return 或异常,事务就回滚了。

解决:看views/purchase.py里审核接口的代码,确认db.session.commit()在approve调用之后、return 之前。如果用了 try-except,except 里要db.session.rollback()。我一般会在开发环境打开 SQLAlchemy 的 echo 日志,SQLALCHEMY_ECHO = True,能看到实际执行的 SQL 和 commit 时机。

5.4 库存数量出现负数

现象:盘点或调拨后,某个配件的库存变成负数。

原因:并发操作时没加行锁,两个请求同时读到quantity=5,各扣 3,最后变成 -1。源码里入库和调拨都用了with_for_update(),但盘点调整那里漏了。

解决:检查confirm_stocktaking方法里更新库存前有没有with_for_update()。没有就加上。另外,数据库层面可以加一个CHECK (quantity >= 0)约束,但 MySQL 5.7 不支持 CHECK 生效,8.0 才支持。所以还是靠代码锁。生产环境建议把隔离级别调到 REPEATABLE READ 以上,MySQL 默认就是,别改成 READ COMMITTED。

5.5 定时任务不执行

现象:库存预警一直不触发,手动调check_stock_alert函数又正常。

原因:APScheduler 的scheduler.start()没被调用,或者 Flask 的 debug 模式下重载器启动了两次 scheduler,导致任务重复或冲突。

解决:确认tasks/scheduler.py在应用工厂create_app里被导入并启动了。debug 模式下加判断:if not app.debug or os.environ.get('WERKZEUG_RUN_MAIN') == 'true': scheduler.start()。或者干脆用flask run --no-reload跑,避免重载器干扰。参数上,interval的minutes=30按需调,但别低于 5 分钟。

6. 二次开发与接口扩展:把系统接进现有流程

源码跑通之后,真正要落地还得做二次开发。我一般会先做一件事:把配件主数据的导入接口写出来。因为客户手里通常有 Excel 格式的配件清单,几千行,不可能手工录。源码里没带导入功能,但parts表结构清晰,加一个POST /api/parts/import接口就行。

# views/parts.py 新增批量导入接口 import pandas as pd @parts_bp.route('/import', methods=['POST']) def import_parts(): file = request.files.get('file') if not file: return jsonify({'code': 400, 'msg': '未上传文件'}), 400 df = pd.read_excel(file) # 期望列:配件编号、配件名称、规格、单位、安全库存 required_cols = ['配件编号', '配件名称', '规格', '单位', '安全库存'] if not all(col in df.columns for col in required_cols): return jsonify({'code': 400, 'msg': f'缺少必要列:{required_cols}'}), 400 success, fail = 0, 0 for _, row in df.iterrows(): part_no = str(row['配件编号']).strip() if Part.query.filter_by(part_no=part_no).first(): fail += 1 continue part = Part( part_no=part_no, part_name=row['配件名称'], spec=row.get('规格', ''), unit=row['单位'], safe_stock=float(row['安全库存']) ) db.session.add(part) success += 1 db.session.commit() return jsonify({'code': 200, 'msg': f'导入完成,成功 {success} 条,跳过重复 {fail} 条'})

逻辑说明:用 pandas 读 Excel,比 openpyxl 省事。required_cols校验列名,因为客户给的 Excel 列名五花八门,先卡一道。part_no去重,已存在的跳过并计数。safe_stock转 float,因为 Excel 里可能是整数也可能是小数。参数上,文件字段名是file,前端用FormData传。这个接口没做事务分批,几千条一次性 commit 没问题,上万的建议每 500 条 commit 一次。

另一个高频需求是库存查询接口。源码里的库存列表是页面渲染,但移动端或外部系统要调 JSON。加一个GET /api/inventory接口,支持按仓库、配件编号、是否低库存筛选。

# views/inventory.py 新增查询接口 @inventory_bp.route('/api/list', methods=['GET']) def api_inventory_list(): warehouse_id = request.args.get('warehouse_id', type=int) part_no = request.args.get('part_no', '') low_stock = request.args.get('low_stock', 'false').lower() == 'true' query = Inventory.query.join(Part) if warehouse_id: query = query.filter(Inventory.warehouse_id == warehouse_id) if part_no: query = query.filter(Part.part_no.like(f'%{part_no}%')) if low_stock: query = query.filter(Inventory.quantity < Inventory.safe_stock) items = query.all() return jsonify({ 'code': 200, 'data': [{ 'part_no': i.part.part_no, 'part_name': i.part.part_name, 'warehouse': i.warehouse.name, 'quantity': float(i.quantity), 'safe_stock': float(i.safe_stock), 'is_low': i.quantity < i.safe_stock } for i in items] })

逻辑说明:join(Part)是为了按配件编号筛选。like模糊匹配,配件编号一般有前缀,比如“BP-”开头,模糊查方便。low_stock参数控制是否只看低库存。返回的is_low字段让前端直接标红,不用再算。参数上,warehouse_id不传就查所有仓库,part_no不传就查全部。分页没做,数据量大了要加limit和offset。

最后说一个验证方法:改完接口后,别急着上生产。先用 Postman 或 curl 跑一遍,确认返回结构和状态码对。然后开SQLALCHEMY_ECHO = True,看实际 SQL 有没有全表扫描。库存表数据量上万后,part_no的模糊查询会慢,给parts.part_no加索引能缓解,但like '%xxx%'用不上索引,要么改成前缀匹配like 'xxx%',要么上全文索引。我一般先看慢查询日志,确认瓶颈在哪再动手。

从那以后我每次拿到一套业务系统源码,都强制走一遍“建库 → 灌数据 → 跑主流程 → 看日志”的流程,不跑通不写代码。这套汽配管理系统的主流程不算复杂,但供应链和仓储的联动逻辑值得细看,尤其是采购单状态机和库存行锁那两块,踩过一次坑就记住了。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 21:46:42

工业网络核心交换原理:MAC学习、广播风暴、环网冗余与VLAN实战

我们搞工业自动化的&#xff0c;对PLC、变频器、伺服这些东西熟悉得很&#xff0c;但一说到网络&#xff0c;很多人其实心里是发虚的。尤其是“交换原理”这四个字&#xff0c;听起来像通信专业才需要啃的硬骨头&#xff0c;跟咱们写梯形图、调PID没什么关系。但实际在产线上跑…

作者头像 李华
网站建设 2026/10/17 21:43:00

Codex + IDE 集成指南:从终端到编辑器,让 AI 无处不在地融入开发流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 21:39:57

爆肝3天,开源AI原生截图神器:拓!可以塞进任何Agent使用~

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 21:39:33

144页Transformer讲义精读指南:从注意力机制到工程实践

简介&#xff1a;这份144页PPT配套视频课程&#xff0c;面向希望系统掌握Transformer架构的NLP学习者、算法工程师与高校师生&#xff0c;从序列建模的经典方法讲起&#xff0c;逐步深入到注意力机制、大语言模型与高效Transformer等前沿内容。包内共1个PDF文件&#xff0c;约5…

作者头像 李华
网站建设 2026/10/17 21:38:20

北京市东城区林内热水器维修服务电话

【北京市东城区林内热水器维修服务电话&#xff5c;欧米到家东城区林内热水器维修】东城区林内热水器维修服务电话 400-996-9791。欧米到家&#xff0c;本地网点&#xff0c;先修后付&#xff0c;2 个网点覆盖东城区。【东城区林内热水器维修 快速信息】 &#x1f4cd; 服务区…

作者头像 李华