资讯动态

ASP.NET进销存管理系统源码实战:从部署到二次开发指南

发布时间:2026/10/7 4:33:08 来源:尧图企业网站定制
简介一套面向制造业与中小企业的ASP.NET进销存管理系统源码基于VS2008与SQL Server 2005开发并集成DXperience v2008界面控件。系统完整覆盖业务、报表、基础数据、系统管理、文件管理和设备管理六大模块业务端包含客户订单、物料需求计划、采购/销售出入库、领料退料、委外加工、收付款及调拨单等企业常用单据流报表端则提供退料明细、采购入库、半成品入库、委外加工进出库、报废统计等多个维度的统计与明细查询同时具备操作员授权、操作日志、系统备份、文件入库与更改单、设备计量器具周检及模具转移等管理功能适合需要学习传统进销存架构或进行二次开发的.NET程序员。资源压缩包共1119个文件、22.98MB主体由538个C#源码文件、197个resx资源和195个resource编译资源构成另含45个DLL动态库、XML/TXT配置文件及少量PDF、DOC说明文档可据此还原完整解决方案并研究报表与权限设计。目前已有462人浏览学习对借鉴BOM分解、委外加工流程和企业权限模型有直接参考价值。1. 一份 ASP.NET 进销存管理系统源码值不值得拿来当项目底座如果你的工作里碰过中小企业的库存管理需求大概率听过或搜过“ASP.NET进销存管理系统源码”这个关键词。它不是一个特定仓库的名字而是一类以 ASP.NET以 WebForms 和 MVC 为主流为技术栈、以商品进销存为业务模型的完整工程。这类源码通常自带商品资料、供应商/客户档案、采购入库、销售出库、库存查询、盘点、报表等模块很多“毕业设计”和“企业信息化改造”的第一版都是从这类工程改出来的。我最早接触这类源码是给一家做五金批发的小公司搭后台老板要的不是花哨界面而是“今天仓库还剩多少、哪些 SKU 低于安全库存、这个月毛利大概多少”。当时直接用一套 ASP.NET WebForms 的进销存工程改数据结构和页面逻辑从拿到源码到能跑通采购-入库-销售-出库-库存报表的完整链路大约花了两天。对想快速搭业务后台、用于课程设计、或想在企业内部做信息化试点的开发者来说这类源码的价值在于业务模型是现成的有真实页面可参考能省掉从零设计表结构和权限体系的时间。但如果你把“源码”当“一键部署就能用”的东西大概率会翻车。连接字符串、IIS 版本、数据库兼容级别、部署路径、甚至 Newtonsoft.Json 的版本冲突都会卡住你。这篇笔记会从业务模型、本地还原、核心参数、避坑经验、二次扩展五个维度把这类源码拆开讲透。2. 先看懂进销存源码的业务骨架分层模型、权限与单据流2.1 三层架构在 ASP.NET 进销存里的真实位置大多数 ASP.NET 进销存源码不是把 SQL 写死在页面里而是按经典的三层架构组织UI 层ASPX 页面或 MVC View、业务逻辑层BLL、数据访问层DAL。UI 层负责交互和输入校验BLL 处理单据流转规则比如“审核后库存才能变动”DAL 负责与数据库交互。如果源码用了 ASP.NET MVC则通常表现为 Controller Service Repository 的组合。理解这套分层对改源码特别重要因为你会发现“改一个按钮的跳转”和“改采购入库的库存逻辑”处在完全不同的层。改按钮跳转只需要动 ASPX 里的 OnClick 事件或前端脚本但改入库逻辑就得动 BLL 层的入库方法而且要确保事务边界在那里不然可能出现“单据保存了但库存没变”的脏数据。在三层之外还有一层基础设施代码值得留意比如 SqlHelper、DBHelper 或基于 EF 的 DbContext。老源码多用 SqlHelper 封装 ADO.NET新一些的用 Entity Framework。前者改 SQL 方便、容易排查性能问题后者写业务代码效率高但踩坑时更难定位。你在拿到源码后首先要判断的是数据访问层属于哪一类这决定了后面所有改动的工作量。2.2 权限模型不是功能而是数据库里那几张表进销存系统的权限通常不是面向过程的而是“用户-角色-菜单/按钮”的模型。用户表User、角色表Role、菜单表Menu、用户角色关联表、角色菜单权限表。当你登录系统后看到的左侧菜单其实是从数据库动态加载的。这种设计的优点是给客户做演示时可以临时把某个角色的某些按钮权限关掉不用改代码。我在实际修改这类系统时至少会把用户表加两个字段LastLoginTime 和 LoginFailCount用于追踪登录状态和做简单的防爆破。很多老源码只有 UserName 和 Password明文或 MD5这种安全性在内部网络勉强能用但如果你准备把系统放到公网至少要升级为加盐哈希推荐 SHA256 或 BCrypt并在登录接口加验证码。2.3 核心业务表与单据状态机从采购到销售的完整链路进销存的核心不是“增删改查”而是“库存账”与“流水账”。采购入库单PurchaseIn审核后库存表Stock增加数量并生成库存流水StockLog销售出库单SaleOut审核后库存减少并记录出库流水。这里的“审核”本质是一个状态字段比如 Status: 0草稿/1已审核/2已作废所有库存变动都发生在审核动作里而不是保存动作里。商品表Product里通常有 CategoryId、Unit、Price、CostPrice 和 SafetyStock 字段。SafetyStock 是安全库存量经典做法是在库存查询页面写一条 SQL当 Stock.Qty Product.SafetyStock 时标记为“低于安全库存”。如果你接到的需求是“缺货提醒要弹窗”而不是“在列表里显示红色”就需要用定时任务扫这张表再接入站内信或钉钉机器人。单据号BillNo是另一个容易让你踩坑的点。老源码喜欢用 DateTime.Now.ToString(yyyyMMddHHmmss) 拼随机数这在单机部署没问题但如果未来要并发导入极有可能生成重复单据号。我一般改单号的生成逻辑为“日期 四位自增流水号”流水号从数据库序列或单独一张计数器表里读保证并发下的唯一性。3. 把源码在本地跑起来环境准备、建库脚本与连接字符串调试3.1 从压缩包里识别工程结构的关键文件你下载到的源码工程一般是 Visual Studio 解决方案格式。先看有没有 .sln 文件有的话直接用 Visual Studio 打开如果只有 .aspx 和 .cs 文件但没有 .sln可以用“打开网站”的方式加载。但更重要的判断依据是找三个东西数据库建库脚本.sql 文件、Web.config 或 App.config、以及 Packages.config如果是 MVC 项目。建议按下面这个顺序检查工程文件分布# 在 Windows 命令行下进入源码目录后执行 tree /F /A | findstr /R sln sql config packages这条命令会列出解决方案文件、数据库脚本、配置文件以及 NuGet 包清单的位置。如果 .sql 脚本缺失你依然可以通过 EF 的自动建库或 Code First Migration 来生成数据库但前提是项目里配置了初始化策略。在确定数据库脚本存在后用 SQL Server Management Studio 执行该脚本。执行前务必确认目标数据库实例的版本SQL Server 2012 生成的 .sql 脚本在 SQL Server 2019 里通常能顺利执行但反向不兼容的情况很常见比如使用了已废弃的语法或排序规则冲突。3.2 修改数据库连接字符串不要只改 Server 名称连接字符串是最容易出问题的配置项它位于 Web.config 的 节点下。很多进销存源码默认的连接字符串是连着远程服务器或本机某个特定实例的你拿到的机器未必有同名实例。下面是我常用的最小修改方案connectionStrings add nameERPConnectionString connectionStringData Source.;Initial CatalogMyERP;User IDsa;Password123456;MultipleActiveResultSetsTrue; providerNameSystem.Data.SqlClient / /connectionStrings这里的 Data Source 是数据库实例名点号表示本机默认实例。如果你的 SQL Server 是命名实例需要写成“.\SQLEXPRESS”之类的格式。Initial Catalog 是数据库名必须与你执行的建库脚本里创建的库同名。User ID 和 Password 建议新建一个专门的应用账号而不是直接用 sa因为 sa 账号通常会在多环境部署时被数据库管理员禁用。调试连接层的有效手段是先用 SQL Server Management Studio 测试账号密码能否登录再在代码里测试。如果你用了 EF有时会因为“EF 初始化策略”而多等几分钟第一次访问页面时看起来像死机其实是在自动建库或迁移。3.3 用 IIS Express 或本地 IIS 部署路径问题的第一个坑如果源码是用 WebForms 写的默认情况下直接按 F5 就能跑但你可能发现登录后跳转到 404。这通常与虚拟路径有关。老源码在写跳转时喜欢用“/Login.aspx”这种根目录绝对路径而你的应用是部署在虚拟目录下的根路径并不等于应用路径。解决方式是把这些绝对路径替换为相对路径或 ResolveUrl 生成的应用相对地址。对于 MVC 项目则要检查路由注册。进销存 MVC 源码默认路由一般是routes.MapRoute( name: Default, url: {controller}/{action}/{id}, defaults: new { controller Home, action Index, id UrlParameter.Optional } );如果没有写 defaults访问根地址就会 404。在部署到 Windows 服务器时还要确认是否安装了 ASP.NET Core 模块或 .NET Framework 4.8 运行时。很多“本地能跑服务器跑不起来”的情况其实是服务器只装了 IIS 没装对应版本的 ASP.NET 运行时。4. 把核心模块改到能用的程度供应商管理、采购入库与库存报表的参数调整4.1 供应商与商品档案编码规则如何影响后续的自动化操作供应商表和商品表是进销存业务的两大基础档案。新增供应商时要注意的是不重复性判断许多源码只用名称做重复判断但现实场景里存在同样名字但不同联系人/不同税号的情况。我会在供应商表加 UniqueKey由名称、联系人、手机号做哈希生成在保存前先查 UniqueKey 是否存在。商品编码ProductNo的生成规则通常在 BLL 层里而不是数据库默认值。最常见的做法是“类别前缀 日期 序号”例如“WJ-20250511-001”。如果你打算未来对接电商系统商品编码必须全局唯一。这个唯一性应该由数据库的唯一索引兜底而不是只靠代码判断否则并发请求在极端情况下仍可能插入重复。修改商品资料页时要注意图片上传控件很多老源码把商品图片存为 Base64 字符串直接塞进数据库。这是坏味道会导致数据库膨胀、页面加载缓慢。建议把图片存到服务器目录数据库里只保存相对路径。我通常会建一个 Upload 目录并按月份分子目录存放避免单目录文件过多。4.2 采购入库的库存更新为什么要在事务里完成采购入库是整个系统里最容易产生数据不一致的模块。一张入库单通常包含表头供应商、入库日期、经办人和明细商品、数量、单价、金额在保存和审核两个环节都要操作多张表。如果代码没包在事务里就可能出现表头保存成功但明细没插入的情况导致“单据打不开但库存已经变了”。以下是典型的审核逻辑伪代码用 C# 描述事务边界using (var transaction connection.BeginTransaction()) { try { // 1. 更新采购入库单状态为“已审核” UpdatePurchaseInStatus(billId, Status.Approved, transaction); // 2. 遍历明细逐条增加库存 foreach (var detail in purchaseInDetails) { IncreaseStock(detail.ProductId, detail.Quantity, transaction); InsertStockLog(detail, StockLogType.PurchaseIn, transaction); } // 3. 提交事务 transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); LogError(ex); throw; } }这里面有一个容易忽略的参数库存上限检查。某些企业不允许某商品库存超过设定上限比如仓容有限或资金占用压力大需要在 IncreaseStock 前查当前库存 本次入库数量是否超过 Product.MaxStock如果超过就抛出业务异常。再加一个参数入库单价是否影响成本价。如果系统配置为“移动加权平均法”那么每次入库审核后商品的 CostPrice 应该变成 (旧库存旧成本价 新入库数量新单价) / (旧库存 新入库数量)。这个计算应该在事务内完成避免出现读取到旧库存和旧成本的时间差。4.3 销售出库与库存扣减负数库存的实际业务含义负库存问题是我调试进销存系统时见过最多的事务性问题。你看一眼这类报表SELECT p.ProductName, s.Qty FROM Stock s INNER JOIN Product p ON s.ProductId p.ProductId WHERE s.Qty 0如果查出了负数记录常见原因是允许超卖、盘点单审核时没修正库存、或退货流程把库存加回但没有校验上限。对于不需要严格库存的行业负库存可以接受但你需要把它做成显式配置。最好的实践是给商品表加 IsAllowNegativeStock 字段销售出库审核时根据该字段决定是否允许扣减到负数。我一般还会在库存扣减的 SQL 语句里加入条件“AND Stock.Qty Quantity”然后用受影响行数判断是否扣减成功。这也是防止并发超卖的有效手段数据库层面的乐观锁比代码里查再改要可靠。代码里先查库存再更新两个请求同时读到库存为 10 时各自扣了 8最终库存变成负数而不是 2这就是典型的并发问题。4.4 库存报表聚合查询的索引设计与时间段过滤库存报表通常是系统最卡的地方尤其是“库存汇总”“销售统计”这类页面。很多源码在报表页面直接对单据明细表做全表聚合扫描数据量超过几万条后明显变慢。一份实用的库存汇总 SQL 通常长这样SELECT p.ProductCode, p.ProductName, ISNULL(SUM(CASE WHEN sl.LogType IN (PurchaseIn, PurchaseReturn) THEN sl.Qty ELSE 0 END), 0) AS TotalIn, ISNULL(SUM(CASE WHEN sl.LogType IN (SaleOut, SaleReturn) THEN sl.Qty ELSE 0 END), 0) AS TotalOut, ISNULL(SUM(CASE WHEN sl.LogType StockCheck THEN sl.Qty END), 0) AS CheckDiff FROM Product p LEFT JOIN StockLog sl ON p.ProductId sl.ProductId AND sl.BillDate StartDate AND sl.BillDate EndDate GROUP BY p.ProductCode, p.ProductName这种 SQL 要在 StockLog 表的 (ProductId, BillDate) 上建复合索引才高效。很多老源码没有这个索引因为建库脚本只加了主键和必要的唯一约束。拿到源码后我建议不要只依赖原有数据库脚本把大表的常用查询条件列出来手动补充索引。要注意的是复合索引的字段顺序是 ProductId 在前还是 BillDate 在前取决于你是“按商品查日期段”更频繁还是“按日期段查所有商品”更频繁。前者用 (ProductId, BillDate)后者用 (BillDate, ProductId)。报表模块的日期过滤如果用的是页面里拼接 SQL 字符串特别容易产生 SQL 注入风险。老源码里大量使用 string.Format 拼接 SQL若参数来自查询字符串或用户输入可以直接被注入。项目如果只在内网跑可能无所谓但如果客户要求上公网至少要改成 SqlParameter 参数化查询。5. 避坑指南跑源码时最常遇到的 5 类翻车现场5.1 登录页面能打开但登录后跳转回登录页现象是用户名密码输对了点登录后浏览器地址栏跳回 Login.aspx 或显示“未授权”。原因通常是 Session 或 Cookie 失效。老源码在 Web.config 里配置了 forms 认证的 timeout但 IIS 应用池的空闲超时时间默认是 20 分钟。如果应用池回收了内存中的 Session 会丢失用户就被踢回登录页。还有可能是代码把用户权限存到了 Session 里但 Session 是在负载均衡环境下没有配 StateServer 或 Redis导致各节点 Session 不共享。解决方式是先把 forms 认证的 cookie 配置稳定再把 Session 模式改为 StateServer 或 SQLServer并在 IIS 应用池高级设置里把“闲置超时”改为 0。如果你只是本地调试把 IIS Express 换成 Visual Studio 自带的开发服务器也能避开一部分 Session 回收问题但不推荐作为长期方案。5.2 数据库脚本执行失败排序规则与用户权限的冲突现象是执行 .sql 脚本时提示“无法解决等于运算中 Chinese_PRC_CI_AS 与 SQL_Latin1_General_CP1_CI_AS 之间的排序规则冲突”或直接报“拒绝了对对象 Product 的 SELECT 权限”。原因是建库时用的排序规则和脚本里某些临时表或变量声明的排序规则不一致。这类问题多出现在跨实例迁移比如从一台简体中文系统的 SQL Server 迁移到英文系统或反过来。解决方式是执行建库脚本前先把数据库的排序规则显式设置为 Chinese_PRC_CI_AS也就是在 CREATE DATABASE 语句里加上 COLLATE Chinese_PRC_CI_AS或者在 SSMS 里修改数据库属性。权限问题则需要给应用账号单独授予对存储过程和表的 EXEC/ SELECT/ INSERT/ UPDATE/ DELETE 权限而不是只加到 db_owner 角色。5.3 报表页面能打开但导出 Excel 乱码现象是在页面上看数据是正常的但点击导出 Excel 后用 Office 或 WPS 打开时中文变成一串乱码或者 Excel 提示格式与扩展名不匹配。原因是老源码生成 Excel 文件时使用 HTML 表格模拟 xls 文件并设置了 Response.Charset utf-8但没有加 BOM。Excel 识别 UTF-8 编码时如果没有 BOM就会默认按 ANSI 解析中文必然乱码。如果用的是真正 xlsx 文件流但数据以 GBK 编码写入同样会出现乱码。解决方式是统一在导出逻辑里设置 Response.ContentEncoding Encoding.UTF8并在输出文件流前写入 BOM 字节。更稳妥的做法是换成 NPOI 或 ClosedXML 这类第三方库生成真正的 xlsx。这个改动需要重新引用 NuGet 包在 .NET Framework 4.5 以上的项目里这不算大改动但有历史包袱的项目会由于依赖冲突而卡住。5.4 页面样式全部丢失静态资源路径写死导致的问题现象是部署到服务器后页面能打开但 CSS、JS、图片全部加载失败按 F12 看到一堆 404。原因是页面里用了绝对路径“/Content/Site.css”而不是相对路径“../Content/Site.css”。当应用部署在站点根目录时没问题部署到虚拟目录如 http://server/erp/后就会找不到资源。MVC 项目通常用 Url.Content(~/Content/Site.css) 或 Razor 的 ~ 语法来规避但老的 WebForms 项目没有统一处理。解决方式是用 ResolveUrl(~/Content/Site.css) 替换所有绝对路径引用或者在页面头部加上 标签指定基准路径。后者最简单但会影响页面内所有相对锚点需要根据具体情况选择。5.5 在服务器上更新了代码但页面还是旧版浏览器缓存与程序集缓存现象是本地改了代码发布后客户说页面还是旧的样子甚至报某个方法不存在。原因分两处浏览器缓存了 .css/.js 文件或者服务器上的 ASP.NET 临时文件没有生成新版本。浏览器缓存是显性的样式不更新时按 CtrlF5 能看到变化。但更隐蔽的是 .NET Framework 编译程序集时bin 目录里残留了旧版本的 DLL 文件新发布的版本没覆盖到位或者有多个同名 DLL。解决方式是发布前先停止应用程序池再把整个发布目录清空然后复制新文件最后启动应用池。不要用“覆盖发布”的方式因为旧的 DLL 可能不会被替换尤其当文件被进程占用时。6. 二次开发的正确姿势从改源码到升级为 ASP.NET Core 的渐进路径6.1 先加审计日志再动核心逻辑老源码最大的通病是“谁在什么时候改了什么”没有记录。如果你要把它改成能真正交付给企业的系统第一步不是重写界面而是在业务逻辑关键节点加审计日志。最简单且侵入性小的做法是使用 AOP 或特性标记。下面这段用 ActionFilter 记录 MVC Controller 请求日志public class AuditLogFilter : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var request filterContext.HttpContext.Request; var user filterContext.HttpContext.User.Identity.Name; var log string.Format([{0}] User:{1} Url:{2} Data:{3}, DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss), user, request.RawUrl, string.Join(;, filterContext.ActionParameters.Select(p p.Key p.Value))); File.AppendAllText(HttpContext.Current.Server.MapPath(~/Logs/audit.log), log Environment.NewLine); base.OnActionExecuting(filterContext); } }这段日志方案是项目起步的兜底方案。如果条件允许更好的是把日志写入数据库表这样后端的查询会更方便。参数方面要注意 ActionParameters 里的模型对象在 ToString() 时可能只会输出类型名需要自己遍历对象的公共属性拼接真正的内容。加完日志后你就可以放心地改库存逻辑了至少出问题时能知道是哪个用户触发了哪次操作。我经历过多次“客户说库存不对”的排查如果系统没有审计日志你只能靠猜。有日志之后直接筛选某用户在某时间段的所有操作很快能定位到是入库重复审核还是退货单漏了审核。6.2 身份认证从 Forms 升级为 JWT 的思路Forms 认证在 WebForms 时代是主流但如果你想把系统拆成前后端分离或是对接微信小程序/App库存接口需要走 Token 认证。要做的改造并不复杂登录接口改为验证用户名密码后签发 JWT前端或客户端把 Token 放在 Authorization 请求头后端用 OWIN 中间件或自定义 HttpModule 解析 Token。JWT 的配置项里有两个参数容易踩坑过期时间exp和签名密钥长度。签名密钥太短会导致部分服务器环境拒绝加载至少 256 位密钥。下面的代码是一个最小可用的签发逻辑var key new SymmetricSecurityKey(Encoding.UTF8.GetBytes(your-256-bit-secret-key-here!)); var creds new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token new JwtSecurityToken( issuer: your-domain, audience: erp-client, claims: new[] { new Claim(ClaimTypes.Name, username) }, expires: DateTime.Now.AddHours(2), signingCredentials: creds ); var tokenStr new JwtSecurityTokenHandler().WriteToken(token);改完后你原本在 Web.config 里的 forms 认证配置可以保留也可以去掉具体取决于你是否还需要给老页面做兼容。如果你同时保留 Forms 认证和 JWT要注意 Authorization 头里的 Token 解析必须在 Forms 认证通过之前完成否则老页面和新接口混在一起时排查问题会非常痛苦。6.3 渐进式迁移 ASP.NET Core 的实践技巧把整套 ASP.NET 进销存系统一次性迁到 ASP.NET Core 是不现实的尤其当源码里使用了大量 WebForms 服务器控件。如果你要往 ASP.NET Core 方向走最常见的路线是保留原有 WebForms 项目不动新增加一个 ASP.NET Core Web API 项目作为数据接口层老页面通过 AJAX 调用新接口读取数据。这样数据库不需要改业务逻辑逐步搬前端页面可以逐步替换。在这种混合架构下连接字符串的管理会分散建议把数据库连接串放到一个共享的配置中心比如使用环境变量或简单的 Json 配置文件。配置示例{ ConnectionStrings: { ERPConnectionString: Data Source.;Initial CatalogMyERP;User IDerp_app;Passwordxxx; } }这里要特别注意ASP.NET Core 项目的默认配置文件名是 appsettings.json而不是 Web.config。如果你同时维护老项目和 Core 项目两边的连接字符串需要保持同步否则由于配置不一致引起的故障在切换期会出现。我的习惯是统一用环境变量来覆盖连接字符串这样在开发、测试、生产三个环境之间切换时不需要改代码只改机器环境变量。6.4 验证一版改动是否影响库存回归测试脚本的设计改完进销存源码后你至少要做一轮核心链路回归不要只点几个页面觉得没报错就完事。我常用的回归测试 SQL 是这样一组断言逻辑先记录当前库存快照到临时表然后跑一遍“新建采购入库单 → 审核 → 确认库存增加”的流程再比对快照和当前库存的差值是否等于单据数量。下面的脚本简化了比对逻辑但思想就是这个-- 创建库存快照 SELECT ProductId, Qty INTO #StockBefore FROM Stock; -- 执行审核操作人工操作或调用存储过程 -- 对比快照与当前库存 SELECT s.ProductId, b.Qty AS OldQty, s.Qty AS NewQty, s.Qty - b.Qty AS Diff FROM Stock s INNER JOIN #StockBefore b ON s.ProductId b.ProductId WHERE s.Qty - b.Qty 0;如果 Diff 不等于预期值说明业务流程中间某个环节出了问题。要特别说明的是这个脚本本身不会自动证明逻辑正确它只是把变化暴露给你。真正要确认业务逻辑正确还是要回到单据明细表和库存流水表逐条核对。我对这类进销存源码的最深体会是它看起来像一份“完整可运行”的项目实际更像一块业务毛坯。你必须有“改三遍”的准备第一遍让它跑起来第二遍把数据一致性和安全性补上第三遍才轮到界面优化和体验升级。如果只做第一遍就交给客户账对不上的问题迟早把你拖回去。希望这篇笔记能帮你把最容易翻车的那几段路先避过去真正把它改造成你自己的可用系统。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑