简介这是一套基于ASP技术的公司产品分类报价管理系统CPLS v4.1完整源码适合正在学习经典Web开发或需要搭建企业产品目录的中级开发者。系统覆盖产品分类维护、商品信息增删改查、多级报价策略、前台搜索过滤、订单跟踪和后台权限控制等核心模块后台还支持角色权限、数据备份恢复前台可按名称、分类和价格范围快速筛选压缩包内核心脚本、页面与素材一应俱全。资源包共465个文件包含96个ASP动态页面、25个HTM静态页、13个JS与7个CSS样式脚本、272个GIF及50个JPG图片素材另附1个Access数据库文件MDB包体仅856KB结构清晰、易于部署。已有165人学习下载。通过这套源码可以完整还原ASPAccess时代的企业报价系统开发思路对照后台管理模块与前台列表、搜索、上传页面能快速理解动态网页与数据库的交互流程适合毕业设计、课程实战或二次开发。1. 一套老ASP源码为什么还有人在找CPLS v4.1提到ASP源码很多人第一反应是“古董”。但在不少企业的内网服务器上CPLS v4.1这类公司产品分类报价管理系统还在持续跑着。它用经典ASPVBScript写成核心就是三件事管分类、管产品、做报价单。win11配置iis asp虽然第一次折腾会有点烦但配好之后这系统比很多新框架都好维护。CPLS v4.1适合两类人一类是要接盘维护老系统的人事/IT工程师另一类是拿它做毕业设计或二次开发样本的开发者。它不带花哨框架数据库结构清晰改个字段就能上线。下面从环境搭建开始把整套源码拆开讲按顺序做就能跑起来。2. 先把运行环境搞定IIS配置与经典ASP兼容性CPLS v4.1是经典ASP源码不是ASP.NET后缀是.asp代码里用的是VBScript偶尔夹一些JavaScript。它不依赖Composer、npm或者.NET运行时整个运行栈非常老Windows IIS ASP引擎 Access或SQL Server。这套组合在2000年代是主流现在依然能跑但需要先把IIS里的ASP功能打开并且关掉几个默认限制。2.1 经典ASP的运行原理与选型浏览器请求index.asp时IIS会把文件交给ASP引擎asp.dll解释执行。服务器端的VBScript代码在页面里用% %包起来执行完成后输出HTML浏览器拿到的只是纯HTML所以查看网页源码时看不到任何服务端逻辑。这也是很多人第一次拿到CPLS源码时在文件夹里找不到“真正代码”的原因——逻辑全在.asp文件里而不是独立后端服务。有人会问现在都流行前后端分离为什么还要用经典ASP对CPLS这种内部报价系统来说数据量不大、并发不高、需求十年没变ASP的维护成本最低。一台旧服务器就能跑不用装Python或Node。而且源码本身就是整包交付改一个加价率、调一个排序规则改完直接覆盖保存刷新就能看到效果。这种即时性反而让很多老工程师愿意维护老系统。如果你拿到的是v4.1大概率数据库是Access的.mdb文件存放在data或database目录下也有少数企业版是SQL Server需要改连接串。这一步先别慌下文两种写法都给了。2.2 在Windows 11 / Windows Server上启用IIS与ASPWindows 11默认没有装IIS要手动启用。Windows Server 2019/2022可以用Install-WindowsFeature家用系统用Enable-WindowsOptionalFeature。下面的命令以管理员身份运行PowerShell# Windows Server 2019/2022 Install-WindowsFeature Web-Server -IncludeAllSubFeature Install-WindowsFeature Web-ASP # Windows 10/11 专业版及以上 Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerRole, IIS-ASP -AllInstall-WindowsFeature的参数是服务器角色名Web-Server会一起装掉默认文档、静态文件、HTTP日志等常用功能Web-ASP单独把ASP引擎装上。Enable-WindowsOptionalFeature里的IIS-WebServerRole对应IIS主体IIS-ASP对应我们需要的脚本引擎。加-All表示连带依赖一起装避免装完缺模块。装完可以在浏览器打开 http://localhost 看是否能出IIS默认页。如果这一步打不开先去“控制面板-程序-启用或关闭Windows功能”勾选“Internet信息服务”下面的“应用程序开发功能-ASP”也要勾上。很多人在win11配置iis asp时卡在这一步就是因为只勾了IIS根节点没有展开勾选ASP。2.3 设置父路径、默认文档与脚本超时IIS装好后ASP还默认不允许页面里用“..”形式的相对路径。CPLS的老代码很喜欢用Include包含conn.asp比如 所以必须启用父路径否则页面会直接500。在IIS管理器中选中站点双击“ASP”功能图标把“启用父路径”由False改为True点击“应用”。用命令行的方式更快在管理员命令提示符里执行%windir%\system32\inetsrv\appcmd.exe set config -section:system.webServer/asp -enableParentPaths:true appcmd set config /section:system.webServer/asp /enableParentPaths:True第一行指定要改的是ASP配置区段enableParentPaths对应“启用父路径”第二行的写法适用于在命令行环境里直接改当前配置。执行完刷新页面原来的500错误一般就消失了。还有一个高频问题ASP页面执行时间超时。报价单计算在数据量大的时候可能超过默认90秒尤其是打印预览时循环几十万条记录。我一般在IIS里把“ASP-限制属性-脚本超时”改成180秒或者运行appcmd set config /section:system.webServer/asp /scriptTimeOut:00:03:00另外默认文档要加index.asp。双击“默认文档”添加一条否则输入http://IP/cpls/ 时浏览器不知道先打开哪个页面。不配置也可以但访问时得手动敲完整文件名客户用着就嫌麻烦。2.4 数据库连接串Access与SQL Server两种写法CPLS v4.1通常把数据库连接信息放在一个公共文件里比如include/conn.asp或data/conn.asp。找到类似下面的代码这就是整个系统的入口% Dim conn, connStr connStr ProviderMicrosoft.Jet.OLEDB.4.0;Data Source Server.MapPath(data\cpls.mdb) Set conn Server.CreateObject(ADODB.Connection) conn.Open connStr %这里Provider指定了OLE DB驱动Jet.OLEDB.4.0对应Access 2003及以下的.mdb文件Data Source用Server.MapPath把相对路径转成服务器物理路径这样写不怕网站目录换位置。如果源码是Access 2007以后的.accdb要把Provider改成Microsoft.ACE.OLEDB.12.0且服务器上要装Access Database Engine。如果是SQL Server版本连接串改成connStr ProviderSQLOLEDB;Data Source127.0.0.1,1433;Initial CatalogCPLS_DB;User IDsa;PasswordyourpasswordProvider换成SQLOLEDBData Source写数据库服务器IP和端口Initial Catalog是数据库名后面是账号密码。升级时我一般把两套连接串都留着用注释切换方便排错时快速定位是库的问题还是代码的问题。这段代码的常见问题是Access库放错路径Server.MapPath找不到文件打开页面会报80004005或者IIS进程没有数据库文件夹的写权限。权限问题留到第5章避坑里细说。3. 读懂CPLS的数据结构分类、产品与报价单拿到源码后第一件事不是急着改页面而是打开数据库搞清楚表关系。CPLS这种老系统做得务实表不会很多但字段命名很口语化比如CategoryName、ProductName、UnitPrice、Discount一看就懂。我拆过几个版本数据结构大同小异核心就是四类表系统用户表、产品分类表、产品信息表、报价单及报价明细表。3.1 三张核心表的职责分类表管“类”产品表管“品”报价单管“一次报价”。三者关系是这样的一个分类下挂多个产品报价单是一次交易或询价的结果包含多个产品每个产品在报价明细里记录当时的单价、数量、折扣。为什么要把价格拆进明细而不是直接写在产品表里因为产品表里的是“标准价”报价单里的是“成交价”成交价会随客户、日期、批量浮动。如果直接覆盖产品表历史报价就全乱了。这个设计是CPLS能长期用下来的关键。用户表通常比较简单存用户名、密码很多老版本是明文这个后面要改、权限级别。权限级别在报价系统里非常重要因为它决定谁能看到成本价、谁只能看销售价。3.2 字段设计与类型选择以下是我在CPLS v4.1里常见的字段结构用表格列出来方便对照表名关键字段类型建议说明CategoryCategoryID自增整型主键CategoryCategoryName文本(50)分类名称如“五金件”CategorySortOrder整型排序号越小越靠前ProductProductID自增整型主键ProductCategoryID整型外键关联CategoryProductProductName文本(100)产品名称ProductModel文本(50)型号规格ProductTechParams长文本技术参数ProductStandardPrice数字(10,2)标准销售价ProductCostPrice数字(10,2)成本价按权限显示QuotationQuotationID自增整型主键QuotationQuotationNo文本(30)报价单号如20250617001QuotationCustomerName文本(100)客户名称QuotationQuotationDate日期报价日期QuotationTotalAmount数字(10,2)总金额冗余用于列表显示QuotationItemItemID自增整型主键QuotationItemQuotationID整型外键QuotationItemProductID整型外键QuotationItemQuantity整型数量QuotationItemUnitPrice数字(10,2)本次成交价QuotationItemDiscountRate数字(5,2)折扣率如0.85数字(10,2)表示10位总长、小数点后2位。金额字段用小数而不是浮点是为了避免二进制浮点带来的0.10.2式误差。折扣用小数表示方便直接乘单价也可以存百分比注意源码里到底用的是哪种用法。3.3 用SQL查询验证数据关系拿到库后先跑几条验证查询能帮你快速确认表名和字段是否与预期一致。在Access的“查询”视图或者用Navicat连接后执行SELECT c.CategoryName, p.ProductName, p.StandardPrice FROM Product p LEFT JOIN Category c ON p.CategoryID c.CategoryID ORDER BY c.CategoryName, p.ProductName;这条SQL作用是把产品和分类关联起来LEFT JOIN保证没有分类的产品也能显示出来虽然正常不会有。ORDER BY先按分类再按产品名排方便检查分类下是否错挂产品。如果两张表里字段名和我列的不一样比如CategoryID变成了cat_ID你需要先打开表设计视图确认再用实际字段名替换。再看报价明细的联动SELECT q.QuotationNo, q.CustomerName, qi.Quantity, qi.UnitPrice, (qi.Quantity * qi.UnitPrice) AS LineAmount FROM Quotation q INNER JOIN QuotationItem qi ON q.QuotationID qi.QuotationID ORDER BY q.QuotationDate DESC;这里用INNER JOIN只保留有明细的报价单计算行金额LineAmount。有些版本的小计金额是预处理存好的有些是实时算的验证这步能看出源码是哪种做法。如果输出结果里LineAmount和页面显示的明细金额对不上说明页面里用了折扣或者改过单价得去查报价单生成逻辑。3.4 附典型表结构SQL脚本如果你想把CPLS的库从Access迁到SQL Server或者从零建一套测试库可以拿下面脚本打底。注意Access的数据类型不完全一样SQL Server里INT、NVARCHAR(50)、DECIMAL(10,2)都是常见用法CREATE TABLE Category ( CategoryID INT IDENTITY(1,1) PRIMARY KEY, CategoryName NVARCHAR(50) NOT NULL, SortOrder INT DEFAULT 0 ); CREATE TABLE Product ( ProductID INT IDENTITY(1,1) PRIMARY KEY, CategoryID INT NOT NULL, ProductName NVARCHAR(100) NOT NULL, Model NVARCHAR(50), TechParams NVARCHAR(MAX), StandardPrice DECIMAL(10,2) DEFAULT 0, CostPrice DECIMAL(10,2) DEFAULT 0, CONSTRAINT FK_Product_Category FOREIGN KEY (CategoryID) REFERENCES Category(CategoryID) ); CREATE TABLE Quotation ( QuotationID INT IDENTITY(1,1) PRIMARY KEY, QuotationNo NVARCHAR(30) NOT NULL, CustomerName NVARCHAR(100), QuotationDate DATETIME DEFAULT GETDATE(), TotalAmount DECIMAL(10,2) DEFAULT 0 ); CREATE TABLE QuotationItem ( ItemID INT IDENTITY(1,1) PRIMARY KEY, QuotationID INT NOT NULL, ProductID INT NOT NULL, Quantity INT DEFAULT 1, UnitPrice DECIMAL(10,2) DEFAULT 0, DiscountRate DECIMAL(5,2) DEFAULT 1, CONSTRAINT FK_Item_Quotation FOREIGN KEY (QuotationID) REFERENCES Quotation(QuotationID), CONSTRAINT FK_Item_Product FOREIGN KEY (ProductID) REFERENCES Product(ProductID) );IDENTITY(1,1)表示自增列从1开始每次加1DECIMAL(10,2)是定点数适合金额。外键约束FK_*把两张表的关系写进数据库防止插入不存在的分类或产品ID。如果建表时报外键错误大都是引用的主键表还没建按Category、Product、Quotation、QuotationItem的顺序执行就行。这里顺带回答一个常被问到的问题ASP能不能搭配MySQL可以。常见做法是安装MySQL ODBC驱动把连接串里的Provider改成MSDASQL或直接用Driver{MySQL ODBC 8.0 Unicode Driver}。但我一般不推荐在生产环境这么干因为老ASP代码里很多SQL语句是Access方言比如IIF、Date()这些到MySQL里会不认改起来比直接换SQL Server更费劲。如果你只是测试那无所谓。4. 看懂CPLS的页面逻辑登录、分类联动与报价计算CPLS v4.1的页面不多但每一页都用了服务端脚本。读这些代码的时候你不要把它当现代框架那样分层看它就是一个页面处理一段流程先取参数再查数据库最后拼HTML。下面挑三个最常被改的地方讲这也是二次开发时动手最多的地方。4.1 登录校验与Session控制登录页login.asp提交用户名密码后跳到checklogin.asp做校验。经典ASP里没有工具函数校验逻辑通常是这样的% Dim username, password username Trim(Request.Form(username) ) password Trim(Request.Form(password) ) If username Or password Then Response.Redirect login.asp?err1 End If Dim rs, sql sql SELECT * FROM Users WHERE Username username AND Password password Set rs conn.Execute(sql) If Not rs.EOF Then Session(UserID) rs(UserID) Session(UserName) rs(UserName) Session(Level) rs(Level) Response.Redirect main.asp Else Response.Redirect login.asp?err2 End If %Request.Form取的是POST表单里的字段 把Empty转成空字符串避免Trim报类型不匹配。SQL里直接拼接用户输入的方式在十年前很常见但存在SQL注入风险下文避坑章节会讲怎么改参数化查询。rs.EOF判断记录集是否到了末尾如果查到记录说明账号密码匹配就写SessionSession(Level)存权限级别后续页面都能用。建议在正式环境上线时把这段里的明文密码改成MD5比对。常见做法是数据库中Password字段存MD5校验时用MD5(password)再比对。别用SHA1或者自己写的算法MD5虽然不完美但在老系统里是改动最小的方案。4.2 产品列表的分类联动下拉分类和产品是父子关系页面上最常见的交互是“选了分类产品下拉跟着变”。经典ASP没有Ajax也能做最简单的做法是页面刷新时带上分类ID服务端根据分类ID查产品列表% Dim catID catID Trim(Request(catID) ) If catID Then catID 0 Dim rsProd, sqlProd sqlProd SELECT ProductID, ProductName FROM Product WHERE CategoryID catID ORDER BY ProductName Set rsProd conn.Execute(sqlProd) % select nameproductID % Do While Not rsProd.EOF Response.Write option value rsProd(ProductID) rsProd(ProductName) /option rsProd.MoveNext Loop % /selectRequest(catID)同时能取QueryString和表单里的参数 把空值转成字符串再Trim这是老代码里常见的防报错写法。catID默认0对应“全部”或“请选择”。SQL里Where CategoryID0查不到数据下拉就是空的这样初次打开页面不会列出全部产品避免数据量太大卡死。如果产品超过几百条我一般会给这个下拉加上optgroup分组把分类名放进去比再开一个分类下拉整齐。但这需要先查所有分类再循环每个分类查产品会变成“1N次查询”数据量大时要小心性能。4.3 报价单生成核心逻辑报价单生成是CPLS的心脏。用户在前台勾选产品、填数量、填折扣后台循环出金额最后汇总写库。下面是一段简化但完整的核心计算流程% Dim i, qty, price, discount, lineAmount, totalAmount Dim discStr totalAmount 0 For i 1 To Request(itemCount) qty CInt(Request(qty_ i)) price CDbl(Request(price_ i)) discStr Request(discount_ i) If Not IsNumeric(discStr) Or CDbl(discStr) 0 Then discount 1 Else discount CDbl(discStr) End If lineAmount Round(qty * price * discount, 2) totalAmount totalAmount lineAmount Response.Write trtd qty /tdtd lineAmount /td/tr Next %这里从表单循环取产品序号对应的数量、单价和折扣。itemCount是前端输出的隐藏字段表示本次报价一共有几行。CInt把数量转成整数CDbl把金额字符串转成双精度数参与计算discStr先做过空值和数值判断不是数字或等于0时按折扣1处理也就是“没填折扣就按原价”。Round(…, 2)保留两位小数避免出现1.9999这类浮点尾巴。真正的落库还要把这些值插入QuotationItem表并把汇总金额更新到Quotation.TotalAmount。建议在插入明细和更新主单之间做事务处理。经典ASP用ADO事务常见写法是conn.BeginTrans和conn.CommitTrans中间任一Insert失败就conn.RollbackTrans否则容易出现主单金额错但明细却没写全的脏数据。这个坑我在第5章会单独拎出来。4.4 页面显示格式微调报价单打印页用户最在意金额的对齐和小数位数。老页面上经常写死FormatNumber(x,2)但不同区域设置下逗号分隔符可能变成点月底对账就很痛苦。一般我会统一用FormatNumber(lineAmount, 2, -1, 0, -1)后三个参数分别表示是否四舍五入-1、是否加负号括号0、是否用千分位-1这样输出样式一致。如果是0元项很多产品经理要求不显示这时判断一下金额是否等于0就行。字体、行距不用动代码里的常量直接在CSS里改CPLS大多数版本用的是自带样式表style.css找到报价单区的class改一行即可。5. 避坑CPLS v4.1部署中最常见的五个问题经典ASP的报错不像新框架那么友好往往是白屏加一句“An error occurred on the server”其余信息被IIS吞了。下面五个问题我在部署CPLS v4.1和类似老系统时都踩过按解决优先级排每个都写清现象、原因和解决。5.1 页面报错500启用父路径与脚本超时现象访问任意.asp页面都出现HTTP 500本地看事件日志能看到“Active Server Pages错误, ASP 0131, 包含文件不能包含…”之类的记录。原因代码里用了 而IIS默认禁用父路径也就是不允许用“..”向上级目录找文件另外连接数据库或循环计算耗时长超过默认90秒脚本超时。解决按第2.3节方法把enableParentPaths设为True脚本超时调到180秒。改完不要只改默认网站如果站点绑定在“网站”节点下要选中具体站点再改IIS配置是按站点继承的。每次改完在IIS管理器右侧点“应用”再刷新页面。5.2 Access数据库被拒绝写入权限与只读库现象登录、浏览正常但新增产品、保存报价单时报“Microsoft JET Database Engine (0x80040E09) 更新失败”或者“数据库或对象为只读”。原因IIS进程w3wp.exe没有数据库文件的修改权限。Access数据库本身就是文件ASP以服务器进程身份去打开它如果该用户的NTFS权限只有读取就只能查不能写还有一种可能数据库文件被设置为只读属性或者程序目录在Program Files下UAC阻止写入。解决把数据库文件.mdb及其所在data目录的NTFS权限给IIS_IUSRS和IIS APPPOOL用户加上“修改”权限。右键文件夹-属性-安全-编辑添加这两个账户并勾选完全控制。同时右键.mdb文件取消“只读”勾选。注意不要给Everyone权限那会引入安全漏洞够用即可。5.3 中文乱码从页面编码到数据库编码现象页面标题是中文但显示成“????”或者乱成一堆符号保存产品名称后再读出来变成问号。原因页面没有声明编码IIS默认按西欧语言解析Access数据库本身有默认编码老的.mdb文件常用ANSI而页面保存时用了UTF-8两边不匹配。解决在每个.asp文件顶部第一行加上% LanguageVBScript CodePage65001 %并把页面的charset设为utf-8。如果是Access数据库可以不用动库文件只要所有页面统一CodePage和charset即可。改完要检查保存文件时的格式用记事本另存为编码选UTF-8。如果数据库里已经乱成问号那只能先导出数据、建新库再导入这个没有后悔药。5.4 表单提交后收不到参数Request取值方式不对现象表单POST提交到下一页但ASP读出来是空值用Response.Write查看Request(username)也是空的。原因页面使用了Request.QueryString(username)去读POST字段。经典ASP中Request.QueryString只读URL参数Request.Form才读POST表单。另外如果表单里的 没有name属性只有id属性提交后后台也是收不到的。解决统一改成Request.Form(username)或者用直接Request(username)自动匹配QueryString、Form、Cookies三种来源但存在被伪造的风险。检查前端HTML确保每个输入框都有namexxx。如果你把form的method改成了get那后端就要用Request.QueryString别混用。5.5 SQL注入与密码明文老系统的安全债现象登录框输入 or 11 能直接进入后台User表里的密码字段一眼能看出来管理员也没改过。原因源码里是直接字符串拼SQL输入没有过滤。这就是经典ASP最普遍的问题。密码明文存放等于把后台大门敞开。解决把关键查询改成参数化用ADODB.Command对象而不是字符串拼接。示例Dim cmd, rs Set cmd Server.CreateObject(ADODB.Command) cmd.ActiveConnection conn cmd.CommandText SELECT * FROM Users WHERE Username? AND Password? cmd.Parameters.Append cmd.CreateParameter(u, 200, 1, 50, username) cmd.Parameters.Append cmd.CreateParameter(p, 200, 1, 50, password) Set rs cmd.Execute200是adVarChar类型第3个参数1表示输入50是最大长度。参数化后输入里的单引号会作为普通字符处理注入就失效了。密码方面至少在代码层写一个MD5函数登录时比对MD5(password)。改完建议强制要求管理员换密码不要用源码自带的默认密码。6. 进阶给报价单加价格分级并让它能导出Excel老系统的权限很多时候只是摆设把产品表和用户表的Level连起来让不同角色看到不同价格是性价比最高的改进。产品表已有CostPrice和StandardPrice用户表已有Level改动只需要一个判断% If Session(Level) 1 Then showPrice rs(CostPrice) Else showPrice rs(StandardPrice) End If Response.Write FormatNumber(showPrice, 2, -1, 0, -1) %Level为1的管理员看到成本价其他角色看到标准销售价。这样导出报表、打印预览都会有同一个逻辑前提是每个页面都先校验Session(Level)不为空否则未登录的人直接打开打印页会报错或看到空值。建议每个需要权限的页面顶部都加上If Session(UserID) Then Response.Redirect login.asp。导出Excel是客户在月底最常求你的功能。经典ASP导出Excel不需要装Office直接让Response以Excel流输出一个HTML表格即可% Response.Clear Response.ContentType application/vnd.ms-excel Response.AddHeader Content-Disposition, attachment; filenamequotation.xls % table border1 trtd产品/tdtd数量/tdtd单价/td/tr % 循环输出报价明细 % /table % Response.End %ContentType换成Excel的MIME类型浏览器收到后就会弹出下载框。注意这个文件本质是HTMLExcel打开时会提示格式不一致这是正常的直接点“是”打开即可。如果你用WPS基本无感。这招做久了我养成了一个习惯每次改完这类老ASP页面都会先用测试库跑一遍全流程再让管理员在真实数据上点一轮。从那以后我改CPLS强制走这个流程出错概率低了很多。这套源码很多年没人动过但你动它之前请先把数据库备份一份改坏了还能退。希望帮到你。本文还有配套的精品资源点击获取