简介全套ASP.NET客户关系管理系统源码压缩包定位为面向.NET平台的企业级Web开发学习与二次改造样板。项目基于Web Forms与LigerUI框架涵盖员工维护、角色授权、合同管理等核心业务页面能够帮助初中级开发者理解CRM系统的分层架构、数据库交互及权限控制实现方式。资源包约47.93MB共2000个文件以951个gif图标、422个js脚本、156个png图片、146个css样式、78个html页面、63个aspx页面、49个cs后端代码为主各类文件分别承担图标呈现、前端交互、页面样式与服务器逻辑等职责另含mdf数据库文件、配置文件与说明文档整体结构清晰便于对照运行和分析。已有134人学习下载。通过研读源码可掌握ASP.NET站点生命周期、Entity Framework等形式的数据访问、基于角色的授权设置以及LigerUI表格排序、分页、动态加载等前端交互写法同时可从hr_employee_add.aspx、Sys_Role_authorized.aspx等典型页面入手梳理大型CRM系统的权限分配和功能组织路径对自行扩展客户管理、销售跟进等功能模块具有直接参考价值。1. 一套能直接落地的ASP.NET CRM源码先搞清楚它能帮你省下什么做CRM系统最怕的不是写不出代码而是从零开始堆需求客户表、销售机会、权限、报表任何一块漏了都要返工。这套ASP.NET客户关系管理系统源码把带ligerUI前端框架的完整WebForms工程打包在一起覆盖客户管理、销售跟踪和系统权限适合三类人要快速交付管理后台的开发者、正在学ASP.NET但缺完整项目参照的学生、接到老系统维护任务想先找同类蓝图的实施人员。它解决的具体问题很直接省掉从零设计表和页面的时间拿到一份能在此基础上改的业务底稿。下面按我实际拆包时的顺序来写先读架构再跑部署最后拆业务和排坑每个环节都会给出落地的判断标准。2. 先把架构看懂ligerUI与ASP.NET三层搭配的选型逻辑在双击解决方案之前建议先花半小时把目录结构看明白。CRM这类管理系统功能多但模式高度重复读懂了分层后面改哪个文件、加哪段逻辑都心里有数。2.1 为什么是ligerUI它把管理后台的重复工作封装成了现成组件这套源码的前端没有上Vue也没有上React而是用了ligerUI。很多第一次接触的人会先怀疑这个框架是不是太老了实际用下来在ASP.NET WebForms的时代里ligerUI是写管理后台效率很高的一套选择。它基于jQuery把表格、树、弹窗、表单、选项卡都做成组件你只要引用JS和CSS就能直接开工不需要npm、webpack这套构建链。对WebForms项目来说这是一个很现实的优势前端不用单独打包发布时把整个Web目录拷到IIS就能跑。ligerUI最核心的组件是ligerGrid。CRM里客户列表、订单列表、权限列表都是表格ligerGrid自带分页、排序、选择行和列宽拖拽后端只需要按约定返回JSON。另一个常用组件是ligerDialog它能加载一个独立aspx页面作为弹窗内容非常契合WebForms里每个页面自带后置代码的写法。这套源码的客户新增和编辑页面大概率就是用它实现的。放到今天看ligerUI的问题也很明显组件默认样式偏老生态早就停更复杂交互要靠自己补。但放到这个场景里它的价值在于稳定且不需要前端环境。你要改的CRM系统绝大多数交互无非就是表格加弹窗加表单这套组件正好覆盖。如果你对界面外观要求很高替换它也不难因为业务数据和页面结构是分离的前端组件换掉不会影响后端逻辑。2.2 解决方案结构从工程目录一眼看出数据是怎么流的把压缩包解开后先不要急着按F5。双击解决方案文件把解决方案资源管理器展开确认工程组织方式。这样一套ASP.NET CRM源码通常按三层或四层划分即使项目名不一样核心也离不开页面、业务、数据这几块。Crm.sln ├─ Crm.Web // 页面层.aspx、.ashx、.asmx和对应的.cs │ ├─ login.aspx │ ├─ default.aspx │ ├─ Customer/ │ │ ├─ CustomerList.aspx │ │ └─ CustomerEdit.aspx │ └─ js/lib/ligerUI/ // ligerUI的js与css资源 ├─ Crm.BLL // 业务逻辑层规则、校验、流程 ├─ Crm.DAL // 数据访问层ADO.NET、SQL执行 ├─ Crm.Model // 实体模型CustomerInfo、SysUser等 └─ db_script.sql // 数据库初始化脚本从这个结构能看出一条清晰的数据流页面不直接写SQL而是new一个BLL对象BLL再调DALDAL负责执行SQL并把结果映射成Model实体最后把实体返回给页面。页面端的ligerUI通过异步请求访问HandlerHandler在服务端包装数据为JSON。我在改动时有个习惯先挑一个最小功能顺着走一遍比如客户列表。从CustomerList.aspx前台到后台的Page_Load再到CustomerManager的List方法看到它调用CustomerDao整个调用链就清楚了。这套分层最大的优点是再动它的时候有人兜底新增字段只要改Model和对应SQL新增页面只要复用BLL里已有的方法。最怕的反而是所有逻辑全写在后置代码页里的那种那种源码拿到手里根本不敢改。2.3 表结构主线客户、联系人与销售机会是怎么关联的CRM的核心是围绕客户组织数据。这套源码里带Customer、Contact、SalesOrder前缀的表优先读。通常客户主表记录公司名称、来源、所属销售和创建时间联系人表挂靠客户销售机会表记录客户从商机到成交的阶段。权限相关表一般以Sys_开头和业务表分开。下面这类项目最常见的建表方式字段名未必和你的源码完全一致但关联关系是完全一致的-- 客户主表 CREATE TABLE Customer ( Id INT IDENTITY(1,1) PRIMARY KEY, Name NVARCHAR(100) NOT NULL, -- 客户名称 Source NVARCHAR(50) NULL, -- 客户来源电话、网络、转介绍 OwnerId INT NULL, -- 销售负责人关联Sys_User表 Province NVARCHAR(50) NULL, Remark NVARCHAR(500) NULL, -- 备注 CreatedAt DATETIME DEFAULT GETDATE() -- 创建时间 ); -- 联系人表一个客户对应多个联系人 CREATE TABLE Contact ( Id INT IDENTITY(1,1) PRIMARY KEY, CustomerId INT NOT NULL REFERENCES Customer(Id), ContactName NVARCHAR(50) NOT NULL, Phone NVARCHAR(30), IsPrimary BIT DEFAULT 0 -- 是否主要联系人 );两张表通过CustomerId关联这是客户管理里最基础的一对多关系。客户详情页通常会在页面上放两个Tab一个显示联系人一个显示销售机会各自按CustomerId过滤。销售机会表会比联系人表多一个阶段字段比如初次接触、方案报价、合同审批、已成交这类字段在报表统计里很重要。读表结构时先看字段注释再看索引和外键。很多老项目为了省事外键约束不建关联完全靠代码保证这种情况下改数据要特别小心别把子表的CustomerId改成不存在的主表Id。遇到这种情况我建议在SQL Server里临时加一个外键约束利用数据库来拦截脏数据而不是靠人工检查。2.4 权限模型用户、角色、菜单和按钮是怎么串起来的后台系统绕不开权限。这套源码的权限属于典型RBAC模型用户归属角色角色关联菜单菜单下再控制按钮。最直接的表现是登录后左侧菜单不同账号看到的内容不一样。表的设计大致是这样。CREATE TABLE Sys_Role ( Id INT PRIMARY KEY, RoleName NVARCHAR(50) NOT NULL ); CREATE TABLE Sys_User ( Id INT PRIMARY KEY, UserName NVARCHAR(50) NOT NULL, Password NVARCHAR(64) NOT NULL, -- 通常存MD5或SHA1 RoleId INT NOT NULL, IsEnabled BIT DEFAULT 1 ); CREATE TABLE Sys_Menu ( Id INT PRIMARY KEY, ParentId INT NULL, -- 父菜单Id为NULL表示顶级 MenuName NVARCHAR(50), Url NVARCHAR(200), SortNo INT ); CREATE TABLE Sys_RoleMenu ( RoleId INT NOT NULL, MenuId INT NOT NULL );权限判断的落脚点在一个基类页面上。每个普通页面继承BasePage而不是直接继承System.Web.UI.Page。基类在页面初始化时检查Session里有没有登录用户没有就重定向到login.aspx有用户就根据RoleId读取菜单集合控制页面元素显隐。这套逻辑写好之后新增页面只需要继承BasePage自动获得登录校验。我在看这类源码时会把Session获取用户信息这个动作当成关键线索顺着它找到当前用户对象被赋值的位置就能理解整个权限体系。遇到权限不起作用的页面优先排查的也是这个基类是否被忽略。架构看完了接下来才是真正动手跑起来。部署这件事百分之九十的问题集中在环境版本、数据库脚本和连接串这三处。3. 把源码跑起来Visual Studio 打开、数据库还原与连接串修改的完整操作3.1 环境清单版本匹配是第一道坎很多人在这一步翻车。下载的ASP.NET源码打不开往往不是因为代码问题而是本机环境和工程目标框架不匹配。先把下面这张表的环境备齐不用全上最新关键是匹配。软件建议版本说明Visual Studio2017或2019社区版安装时勾选ASP.NET和Web开发工作负载.NET Framework对应工程TargetFramework4.0、4.5、4.6等版本要和工程一致SQL Server2008R2及以上Express版够用重点是有SSMSIIS ExpressVisual Studio自带本地调试直接用特别强调一下这是传统ASP.NET WebForms工程不是ASP.NET Core。如果你机器上只装了.NET Core的SDK直接打开这个解决方案大概率会提示目标框架缺失。解决办法不是去装一堆奇怪组件而是打开Visual Studio Installer勾上ASP.NET和Web开发负载再确认.NET Framework相关组件被选中。装完重启VS重新打开工程一般就好了。3.2 还原数据库执行SQL脚本和附加.mdf二选一打开压缩包找db_script.sql、database目录或.mdf文件。有.sql优先执行脚本只有.mdf就用附加的方式。两种方法都可行但脚本更可控因为你能看到系统建了哪些表、插入了哪些初始数据。在SSMS里连接本地实例新建一个查询窗口把db_script.sql内容整个拖进去全选执行。命令行方式也可以sqlcmd -S .\SQLEXPRESS -i db_script.sql参数说明-S指定SQL Server实例名.\SQLEXPRESS表示本机默认的SQLEXPRESS实例-i指定要执行的SQL文件。如果脚本里带了CREATE DATABASE语句执行完会自动建库。如果脚本只建表不建库需要先手动建一个空库再用USE语句切换或者直接在SSMS里把执行对象切换到目标库。只有.mdf文件时在SSMS左侧数据库节点上右键选择附加添加.mdf文件。需要注意同一目录下通常有同名的.ldf日志文件附加时一并选择否则可能报错。如果提示数据库版本比当前实例版本高说明SQL Server版本太老要么换高版本要么找原始实例导出脚本。3.3 修改连接串这一步错一个字符就全盘连不上数据库起来了接下来把连接串改到本地环境。这类源码的连接串通常写在Web.config的connectionStrings节点下名称可能是conn_str、CrmDB之类。不要只改一处把整个Web层的配置文件都搜一遍。connectionStrings add nameCrmDB connectionStringData Source.\SQLEXPRESS;Initial CatalogCrmDB;User Idsa;Password123456; providerNameSystem.Data.SqlClient / /connectionStrings逐项解释Data Source是数据库实例.\SQLEXPRESS指向本机SQLEXPRESSInitial Catalog是库名要和实际数据库名一致User Id和Password是SQL Server登录账号。如果你用的是Windows身份验证就把User Id和Password删掉改成Integrated SecuritySSPI此时不再需要用户名密码。提示如果源码里连接串出现多处一定要全部替换漏一处就会出现有的页面正常、有的页面报数据库错误的奇怪现象。这里最常见的坑是连接串写localhost但本机装的是命名实例实际应该是.\SQLEXPRESS或者数据库密码含特殊字符XML里没有转义又或者源码里同一个连接串在多个配置文件中都出现只改了Web.config漏掉了别的。我的做法很简单先用SSMS用同一组账号密码连一次本地实例SSMS能连上代码里就一定能连上。如果连不上问题根本不需要调试代码先把数据库连接解决。3.4 编译启动F5前先确认默认账号和登录逻辑连接串改好后按F5浏览器会打开登录页。此时你还差一组能进系统的账号。源码说明里通常会写默认管理员账号没写就去数据库用户表查SELECT Id, UserName, Password, RoleId, IsEnabled FROM Sys_User WHERE IsEnabled 1;查到结果后在登录页输入账号密码。这里会遇到三种情况第一密码是明文直接输入第二密码是MD5或SHA1你需要把输入框里的密码用相同的哈希算法转换后和库里的值比对第三登录页有首次启动的初始化逻辑会自动创建默认管理员账号。遇到第二种情况有个实际的办法先在库里找到一条已知账号记录把Password字段改成你自己生成的MD5值再拿明文密码登录。我第一次拆这类项目时就卡在登录页折腾了半天最后发现是验证码逻辑在读取Session变量时名字写错了。遇到登录不进后台先打开浏览器F12看Network里的请求返回后端通常会直接返回用户名或密码错误之类的信息对症处理比闷头猜快得多。如果登录能过但跳转后菜单显示不全别慌这是权限数据问题。去Sys_RoleMenu表看管理员角色有没有把菜单都关联上很多源码用SQL脚本初始化数据时不会把每个菜单都挂到管理员身上需要自己补关联。4. 业务功能拆解客户列表、新增弹窗与Session权限的代码落点前面几章把源码跑起来了接下来要做的是真正理解业务代码这样才能改。CRM的业务看似多实际是围绕客户展开的列表页做筛选和分页详情页展示关联数据编辑页做新增和修改最后用权限控制访问范围。这一章我会挑三个最常见的代码落点拆开讲。4.1 客户列表ligerGrid初始化与后端返回JSON的约定打开CustomerList.aspx你会发现页面本身几乎没有业务HTML主体只放了一个div然后通过脚本初始化ligerGrid。$(#maingrid).ligerGrid({ url: /Ashx/CustomerHandler.ashx?actionlist, // 数据接口 height: 400, usePager: true, pageSize: 20, columns: [ { display: 客户名称, name: Name, width: 200 }, { display: 客户来源, name: Source, width: 100 }, { display: 负责人, name: OwnerName, width: 100 } ] });这段代码的要点在url和columns。ligerGrid初始化后会自动向url发起请求请求参数里带有分页信息页码和每页条数。columns数组里的name值必须和后端返回JSON里的字段名完全一致不一致时表格能加载但整列空白。后端Handler返回JSON需要遵循ligerGrid的约定大概是这样的结构{ Rows: [ { Name: 某某科技有限公司, Source: 网络推广, OwnerName: 张三 } ], Total: 1 }Rows是当前页的数据数组Total是符合条件的数据总条数。表格拿到Total后计算总页数生成底部分页栏。这里最常见的错误是后端只返回一个数组没有Total结果表格一页都显示不出来或者分页条显示得奇怪。4.2 后端Handler.ashx怎么接收参数并返回JSON.ashx是ASP.NET里的通用处理程序适合做这种轻量接口。前端请求的参数都集中在context.Request里处理完数据后直接把JSON字符串写回响应。public void ProcessRequest(HttpContext context) { string action context.Request[action]; if (action list) { string keyword context.Request[keyword] ?? ; int pageIndex int.Parse(context.Request[page] ?? 1); int pageSize int.Parse(context.Request[pagesize] ?? 20); CustomerManager manager new CustomerManager(); var result manager.GetCustomerPage(keyword, pageIndex, pageSize); context.Response.ContentType application/json; context.Response.Write(JsonConvert.SerializeObject(new { Rows result.Items, Total result.Total })); } }这个接口的做法是先取action判断业务类型再取查询参数调用BLL层方法最后把分页结果包成匿名对象序列化为JSON。这里的JsonConvert.SerializeObject用的是Newtonsoft.Json绝大多数ASP.NET项目都引入了这个库没有的话通过NuGet安装即可。参数说明keyword是模糊查询关键字page和pagesize来自ligerGrid自动带来的分页参数。我的习惯是在Handler入口统一做参数默认值处理避免空值和类型转换异常。如果某个字段值为中文且出现乱码检查Response的ContentType是否带charsetutf-8常见写法是application/json; charsetutf-8。4.3 新增客户ligerDialog加载独立编辑页新增客户在ligerUI里的标准做法是用ligerDialog打开一个新增页面保存成功后关闭弹窗并刷新grid。function addCustomer() { $.ligerDialog.open({ url: /Customer/CustomerEdit.aspx, title: 新增客户, width: 520, height: 380, isResize: true, onClosed: function () { $(#maingrid).ligerGrid(reload); } }); }onClosed回调非常关键它在弹窗关闭后触发让列表重新加载数据。否则新增完数据却看不到。很多初次使用ligerUI的人在弹窗里保存成功但列表不刷新就是这个回调没写。CustomerEdit.aspx页面的保存按钮后台代码一般是这样接收表单的string name Request.Form[Name]; string source Request.Form[Source]; string remark Request.Form[Remark]; CustomerManager manager new CustomerManager(); CustomerInfo info new CustomerInfo { Name name, Source source, Remark remark }; bool ok manager.Add(info); Response.Write(ok ? success : fail); Response.End();这段代码的动作很直白从Request.Form里取值拼成实体交给BLL返回结果。布尔值判断里我习惯多带一个具体错误信息不然批量加数据时失败原因不明。ligerForm布局的name属性要和这里的Name、Source一一对应对应不上时后台收到的是null这也是新增后莫名少字段的常见原因。4.4 权限控制Session判断与页面基类CRM系统里权限分两层一是登录判断二是数据范围。登录判断通常在基类里完成避免每个页面重复写。public class BasePage : System.Web.UI.Page { protected int CurrentUserId { get; private set; } protected override void OnInit(EventArgs e) { if (Session[UserId] null) { Response.Redirect(/login.aspx); return; } CurrentUserId (int)Session[UserId]; base.OnInit(e); } }所有业务页面把继承System.Web.UI.Page改成继承BasePage登录保护就自动生效。CurrentUserId属性存的int从Session里取出来是object必须强转不然会有编译错误。这个基类写好后新页面只需要写业务逻辑不需要操心登录校验。数据范围权限是这层之上的业务规则普通销售只能看到OwnerId等于自己Id的客户经理能看到整个部门。这套源码里一般通过SQL拼接实现。我倾向于把它放在DAL层做一个统一过滤不在每个页面重复拼否则漏掉一处就会越权。如果你拿到源码后想测试角色隔离先开通一个普通销售账号用它的权限登录翻一遍页面哪里有漏洞很快会暴露。这一章拆的是最常见的三个点。剩下的订单、跟进、统计报表套路基本一致先确认前端用什么组件再找对应的Handler或后台方法最后看SQL条件。跑通了一个模块等于跑通了整个系统。5. 常见问题与避坑编译报错、连接串失效与ligerUI白屏的实战排查这里把本地环境里出现的典型问题逐条列出来每条按现象、原因、解决三步写供你按图索骥。这些问题是我在实际部署这类ASP.NET管理后台时遇到最多的情况其中几条属于典型的老项目玄学表面症状在页面根源却在环境。5.1 现象一打开工程满屏编译报错在Visual Studio里打开解决方案后错误列表里出现大量找不到命名空间或类型、未能解析引用的程序集。原因很可能是目标.NET Framework版本没安装或者工程引用的第三方DLL没有随源码一并提供。先看工程属性里的TargetFramework再去控制面板确认已安装的.NET Framework版本。如果目标版本为4.5机器上只有4.7兼容也可能存在问题尤其老项目。第三方DLL缺失的话找到Libs或packages目录手动添加引用。我的习惯是先把错误列表第一行解决很多后续错误是连锁反应并不真的是新问题。5.2 现象登录页能打开提交后提示数据库连接失败页面本身能渲染说明Web层文件和编译没问题。连接失败基本是连接串、SQL Server实例、账号密码三者中的一个。先打开SSMS用Web.config里的同一组账号密码连数据库如果SSMS连不上问题就不在代码。常见原因是密码改了但连接串没同步或者实例名写错成localhost。还有一个隐蔽问题SQL Server默认只有Windows身份验证没有启用SQL Server和Windows身份验证模式User Idsa这种连接会直接拒绝。解决方式是在SSMS服务器属性的安全性页签里勾选SQL Server和Windows身份验证模式然后重启SQL Server服务。防火墙挡端口也是常见原因SQL Server默认端口1433如果不通代码和SSMS都会超时。5.3 现象ligerUI表格一直显示加载中没有任何数据表格请求发出去但没拿到数据或格式不对。打开浏览器F12的Network面板看请求的真实返回。原因通常有几个接口地址url写错或没有action参数后端返回的是HTML页面不是JSONJSON字段名和columns里name不一致Total和Rows的大小写和ligerUI要求不一致。解决办法是把返回内容复制出来用在线格式化工具看一眼结构确认是否带Rows和Total两个元素。如果返回200但内容是字符串无论看起来多像数据也要先确认ContentType是不是application/json。ligerUI对纯数组的支持有限沿用它的分页结构最稳。还有一个容易忽略的点Handler的类名被改过但前端url没同步更新这类问题看Network的请求地址一眼就能定位。5.4 现象登录成功后马上跳回登录页常见于Session失效、Cookie域名不一致、判断逻辑写错。先看浏览器Application面板里Session对应的Cookie是否存在。如果每次请求都产生新的SessionId说明Session没被保存原因往往是Web.config里SessionState配置异常或服务器进程回收频繁。还有一种情况登录页跳转成功后目标页面的基类Session判断在登录代码保存Session之前执行。检查登录成功后是不是先写了Session[UserId]再Redirect顺序反了会一直循环跳转。有些项目还用了FormsAuthentication如果Cookie名称和登录路径配置不一致同样会反复跳转。这种问题用断点走一遍比肉眼猜要快我在登录方法里设断点看Session到底有没有写进去通常几步就能定位。5.5 现象发布到IIS后弹窗、样式全部丢失本地开发一切正常发布到IIS后脚本、CSS全部404原因大都是部署方式和路径问题。WebForms项目发布时没有把scripts、css、js这些静态目录同时拷过去或者拷贝后放在子目录而页面引用的路径是根目录下的相对路径。解决是在发布配置里把静态资源目录一并包含。如果服务器上有多个站点的虚拟目录location路径要额外小心比如应用部署在/crm/下绝对路径/xx.js指向的是站点根目录此时应该用相对路径或Page.ResolveUrl动态拼。另外检查IIS应用池是否启用了32位应用程序以及托管管道模式是经典还是集成这会影响某些Handler的注册。经典模式下.ashx的处理程序映射需要单独配置集成模式下通常不用。这五个问题是我在这类ASP.NET管理后台里踩得最多的。前几年有一次给客户部署CRM卡在连接串上近两小时最后发现是配置里多了一个空格从那以后我每份连接串都用SSMS先验一遍再贴进去。6. 进阶不动原架构给CRM补一个客户跟进记录模块当你把客户列表、编辑、删除都跑通后最常见的业务需求就是给每个客户加跟进记录。下面用一个最精简的模块演示如何在不改架构的情况下扩展涉及建表、列表、接口三层。6.1 建表并进行数据初始化CREATE TABLE FollowRecord ( Id INT IDENTITY(1,1) PRIMARY KEY, CustomerId INT NOT NULL, -- 关联Customer.Id FollowTime DATETIME NOT NULL DEFAULT GETDATE(), Content NVARCHAR(500) NOT NULL, -- 跟进内容 CreateBy INT NOT NULL, -- 创建人 NextTime DATETIME NULL -- 下次跟进时间 ); CREATE INDEX IX_Follow_CustomerId ON FollowRecord(CustomerId);FollowRecord挂在CustomerId下一对多和Contact的关系一样。加索引是因为客户详情页频繁按CustomerId查列表。6.2 在详情页挂一个Tab并复用列表套路在客户详情页找到Tab定义位置新增一个跟进记录页签里面放一个空div用ligerGrid加载$(#followGrid).ligerGrid({ url: /Ashx/FollowHandler.ashx?actionlistcustomerId customerId, height: 300, columns: [ { display: 跟进时间, name: FollowTime, width: 150 }, { display: 跟进内容, name: Content, width: 300 }, { display: 下次跟进, name: NextTime, width: 150 } ] });Handler里的实现可以直接照抄CustomerHandler的骨架只是把查询语句换成按CustomerId过滤同时注意把查询参数customerId转成int防止SQL注入和类型异常。新增记录时在弹窗里保存后重新加载followGrid。注意ligerGrid的刷新支持带条件$(#followGrid).ligerGrid(reload, { customerId: customerId });这样可以保留当前客户过滤条件不会刷新成一页全量数据。这套源码最大的价值不是某个现成功能而是一套可以复制的模块套路。我从这套源码里学到的经验是拿到老项目的正确顺序不是先改界面而是先建测试账号把每一个功能点都点一遍记录它原本的行为再动手改。第一次我拿到类似的CRM源码第一件事就是换logo改样式结果脚本路径改坏导致登录页白屏最后花一个晚上回退。从那以后我每次接手这类ASP.NET管理后台源码都会先跑通原版并做一次完整功能盘点再开始做任何改动。希望这个习惯能帮到你。本文还有配套的精品资源点击获取