ARTICLE DETAIL

资讯详情

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

SSM家装平台开发实战:从数据库设计到部署避坑全攻略

SSM家装平台开发实战:从数据库设计到部署避坑全攻略 简介该资源是基于SSM框架开发的暖心家装平台完整项目源码包面向Java方向毕业设计、课程设计与期末大作业场景适合需要快速搭建可演示的家装类Web系统的学生参考。压缩包共2000个文件约101.68MB包含178个Java源文件、174个JSP页面、318个JavaScript脚本、114个CSS样式表及89个XML配置等目录结构完整涵盖在线设计、材料选择、预算评估、施工监理与数据库管理模块便于按模块阅读与二次开发。已有31人浏览学习项目整合Spring、SpringMVC、MyBatis三层架构并配有SQL脚本和前端资源对理解SSM整合流程、家装业务数据建模及前后端交互具有直接参考价值。1. 先说清楚这个SSM家装平台到底是什么值不值得做如果你正在挑毕设题目或 Java Web 练手项目“基于 SSM 的暖心家装平台设计”这个标题看着像普通管理系统实际动手后会发现它比常见的商城、博客系统多一层麻烦既要管装修案例这种内容展示又要处理业主预约、设计师接单、线下量房、报价跟进这种连续业务。一个项目同时踩到 Spring 的对象管理和事务、SpringMVC 的数据绑定与拦截、MyBatis 的动态查询和参数绑定恰好能把 SSM 三个框架都用到真正疼的位置。这个项目适合两类人一类是要选毕设题目的学生想找功能完整、答辩能讲清楚、代码能自圆其说的方向另一类是刚学完 Java Web 的初学者想用一个真实系统把三个框架串起来。我按照自己做过同类项目的思路把从建表到能演示的完整路径写在下面尽量让你看完能直接动手复现。2. 跑通SSM家装平台的最小骨架从数据库设计到项目结构很多人在 SSM 项目里第一步就卡住不是代码写不出来而是表和表之间的关系没想清。家装平台里有业主、设计师、管理员三类人也有案例、预约、报价、评价四类核心数据。把关系理顺后面所有接口都顺。2.1 数据库六张核心表业务关系要提前定死我一般不用复杂的权限表设计直接用一个用户表加角色字段区分三类账号再加一张设计师扩展表存从业年限和擅长风格这样既好写 SQL答辩也好讲。下面这几张表是常见做法你可以按需加减字段create table t_user ( id int primary key auto_increment, username varchar(50) not null unique, password varchar(100) not null, real_name varchar(50) default null, phone varchar(20) default null, role tinyint not null default 2, -- 0管理员 1设计师 2业主 create_time datetime default current_timestamp ) engineInnoDB default charsetutf8; create table t_designer ( id int primary key auto_increment, user_id int not null, avatar varchar(200) default null, specialty varchar(100) default null, years int default 0, intro varchar(500), status tinyint default 0, -- 0待审核 1正常 2冻结 constraint fk_designer_user foreign key (user_id) references t_user(id) ) engineInnoDB default charsetutf8; create table t_case ( id int primary key auto_increment, title varchar(100) not null, cover_img varchar(200) default null, style varchar(30) default null, area decimal(10,2) default null, budget decimal(12,2) default null, designer_id int not null, description text, create_time datetime default current_timestamp, constraint fk_case_designer foreign key (designer_id) references t_designer(id) ) engineInnoDB default charsetutf8; create table t_appointment ( id int primary key auto_increment, user_id int not null, designer_id int not null, case_id int default null, appoint_time datetime not null, address varchar(200) not null, note varchar(500), status tinyint not null default 0, -- 0待受理 1已确认 2已量房 3已完成 4已取消 create_time datetime default current_timestamp, constraint fk_appt_user foreign key (user_id) references t_user(id), constraint fk_appt_designer foreign key (designer_id) references t_designer(id) ) engineInnoDB default charsetutf8;剩下两张是报价表 t_quote 和评价表 t_comment报价表关联预约单存报价金额、报价文件路径、状态评价表关联预约单和用户存评分和文字评价。两张表属于附加业务没有它们系统也能跑但答辩时功能就少了一层。这套设计的关键在于预约表同时关联业主和设计师把一次业务会话当成一条记录来跟踪状态案例表挂在设计师下面方便在案例详情页直接看设计师信息。把外键建好之后MyBatis 里做嵌套查询或联表查询都方便后面预约单要展示业主姓名和设计师姓名时一条 join 就能查出来。2.2 用Maven搭出三层骨架依赖版本怎么选SSM 的老问题就是依赖打架。我一般用 Spring 5.x、MyBatis 3.5.x、mybatis-spring 2.x 这组组合JDK 用 8Tomcat 用 8.5 或 9能少踩很多坑。pom.xml 核心依赖如下properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target spring.version5.3.x/spring.version mybatis.version3.5.x/mybatis.version /properties dependencies dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.1.x/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.x/version scoperuntime/scope /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.x/version /dependency /dependencies这里我把 spring-jdbc 也加进来了因为 Spring 管理 MyBatis 事务时需要它。Druid 负责连接池整体思路是“DataSource 交给 Spring 创建SqlSessionFactory 用 mybatis-spring 提供的 SqlSessionFactoryBean 装配”你不需要手写 MyBatis 工具类。版本背后的逻辑是Spring 5.3 系列在 5.x 里属于长期维护分支后续 6.x 要求 JDK 17 起的版本对开发环境不太友好MyBatis 3.5 系列和配套的 mybatis-spring 2.1 系列兼容性已经非常收敛。如果你还在用 Spring 4 和 mybatis-spring 1.x也能跑但 Spring 4 的老包名在 Tomcat 9 上会有兼容风险所以新项目直接上这组版本更稳。具体的小版本号以你本地 Maven 中央仓库能拉到的为准不用刻意追新。2.3 第一行能响应的代码把案例列表查出来骨架搭好后先写一个最小链路验证连通性。Controller、Service、Mapper 三个文件按经典分层写Controller RequestMapping(/case) public class CaseController { Autowired private CaseService caseService; RequestMapping(value /list, method RequestMethod.GET) ResponseBody public ListCase list() { return caseService.listRecent(10); } }Service public class CaseService { Autowired private CaseMapper caseMapper; public ListCase listRecent(int limit) { return caseMapper.selectRecent(limit); } }public interface CaseMapper { ListCase selectRecent(Param(limit) int limit); }对应的 Mapper XML 里写一条最简单的 SELECTselect idselectRecent resultTypecom.example.entity.Case select id, title, cover_img, style, area, budget, designer_id, description from t_case order by create_time desc limit #{limit} /select这段逻辑里有两个要点Controller 用 ResponseBody 返回对象SpringMVC 借助 Jackson 自动序列化成 JSON前端案例列表页直接拿 Ajax 数据渲染Service 层目前是透传但后面预约、报价的复杂事务都要在这一层写 Transactional所以先留出来。还有一点必须注意实体类属性如果是驼峰命名比如 coverImg而数据库字段是 cover_img需要在 mybatis-config.xml 里开启 mapUnderscoreToCamelCasetrue或者在 SQL 里写别名否则值赋不上去。启动 Tomcat 后访问 /case/list能返回一段 JSON 数组才说明 Spring、SpringMVC、MyBatis 三个框架真正握上手了这个黑匣子一旦打开后面写功能就有底了。3. 把“暖心”做成功能预约与接单的完整链路内容展示页面做完后平台的核心是预约流程。业主看到案例心动了发起预约设计师在后台看到待受理单确认后约量房。这条链路在答辩里是高频提问区状态怎么流转、参数怎么校验、查询怎么做都是送分题也是送命题。3.1 预约订单状态机五种状态不能乱跳预约单状态用 tinyint 存数字但代码里最好用枚举避免魔法数字到处飞。我见过有人直接在 Controller 里写 if (status 3)需求一变谁都改不了。用枚举把状态机固定在业务层public enum ApptStatus { PENDING(0, 待受理), CONFIRMED(1, 已确认), MEASURED(2, 已量房), DONE(3, 已完成), CANCELED(4, 已取消); private final int code; private final String desc; ApptStatus(int code, String desc) { this.code code; this.desc desc; } public int getCode() { return code; } public String getDesc() { return desc; } public static ApptStatus of(int code) { for (ApptStatus s : values()) { if (s.code code) return s; } throw new IllegalArgumentException(未知状态: code); } }状态流转要有“谁允许谁”的规则。常见做法是业主提交预约后是 PENDING设计师确认后变 CONFIRMED量完房设计师在后台标记 MEASURED业主确认方案并线下付款后变 DONE业主在 PENDING 或 CONFIRMED 时可以取消。关键坑位是 CANCELED 和 DONE 必须是终态不能再回到 PENDING否则报表统计会乱。业务层做一个状态变更方法先查旧状态再判断是否允许迁移不允许就直接抛异常提示“当前状态不可执行此操作”。枚举在页面上回显时也很方便直接调用 getDesc() 就能显示中文不用再写一堆 if 判断。3.2 参数校验不能只靠前端后端必须拦两道预约表单里最有价值的是联系时间和房屋地址。有些同学把校验全部放在 jQuery 插件里后端接口裸奔随便传空字符串也能写库。答辩时老师只要在浏览器控制台里改掉提交数据马上露出问题。后端校验最少做两件事必填非空、长度上限和格式校验。手动校验虽然啰嗦但可控性强RequestMapping(value /submit, method RequestMethod.POST) ResponseBody public Result submit(RequestParam Integer userId, RequestParam Integer designerId, RequestParam String appointTime, RequestParam String address, RequestParam(required false) String note) { if (userId null || designerId null) { return Result.error(用户或设计师不能为空); } if (address null || address.trim().isEmpty() || address.length() 200) { return Result.error(地址不能为空且不能超过200字); } if (appointTime null || !appointTime.matches(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2})) { return Result.error(预约时间格式应为 yyyy-MM-dd HH:mm); } appointmentService.submit(userId, designerId, appointTime, address, note); return Result.ok(); }这里用正则校验时间格式是必要的SpringMVC 默认把字符串转成 Date 时对“2024-05-20 14:30”这种格式支持得并不好直接绑定到 Date 字段容易报 400。与其在类型转换里折腾不如先用字符串接收校验通过后再手动 parse。你还可以把手机号、预算这类字段的校验规则沉淀成一个静态方法Controller 里只调不写代码短一半。前端校验只是体验层面的东西后端校验才是系统安全的底线答辩时老师重点看的就是这个底线守没守住。Result 对象是你自己的统一返回体结构就是 code、message、data 三个字段前后端约定好就行。3.3 列表页的筛选查询MyBatis 动态 SQL 是主力业主端要按风格、面积、预算筛案例设计师端要按状态筛预约单。这些筛选项组合起来SQL 写死是不可能的。MyBatis 的动态 SQL 在这里就是主力配合手写分页参数效果非常直观select idselectCases resultTypecom.example.entity.Case select id, title, cover_img, style, area, budget, designer_id, description, create_time from t_case where if teststyle ! null and style ! and style #{style} /if if testminArea ! null and area gt; #{minArea} /if if testmaxBudget ! null and budget lt; #{maxBudget} /if /where order by create_time desc limit #{offset}, #{pageSize} /select标签会自动去掉第一个多余的 and所以前面几条条件不用纠结前缀。这里我特意写了 和 因为 XML 文件里 和 是特殊字符直接写会报 XML 解析错误这是新手上路必踩的一脚。limit #{offset}, #{pageSize} 是手写分页的方式offset 从 0 开始每页几条由 pageSize 决定。如果你想用 PageHelper 也可以但手写对答辩更友好老师一眼能看懂而且不会出现拦截器把分页 SQL 拦截坏的奇奇怪怪的问题。如果做的是老式 JSP 页面那要把筛选条件放到 Model 里回显否则翻页后条件丢失如果做的是前后端分离的 JSON 接口 里的条件直接对应前端传来的对象字段。动态 SQL 在 MyBatis 里的地位相当于“可以组装的长串 SQL 模板”用好了列表页基本不愁。4. 管理端与文件上传把案例图和报价单落地功能链路通了一半后管理端是另一个重头装修案例要有封面图设计师要传报价单。文件上传、后台登录拦截、Excel 导出这三块是 SSM 项目里最常被问到的实操点。把这几样搞定项目就不只是增删改查的展示层玩具。4.1 文件上传本地存储的最简方案毕设项目引 OSS 有点重本地存文件是常见做法。写一个上传接口接收 MultipartFile把文件写到项目的 upload 目录文件名用 UUID 重命名防止中文名和重名问题RequestMapping(value /upload, method RequestMethod.POST) ResponseBody public Result upload(RequestParam(file) MultipartFile file, HttpServletRequest request) { if (file.isEmpty()) { return Result.error(文件为空); } String originalName file.getOriginalFilename(); String ext originalName ! null ? originalName.substring(originalName.lastIndexOf(.)) : .jpg; String fileName UUID.randomUUID().toString().replace(-, ) ext; String realPath request.getServletContext().getRealPath(/upload/); File dir new File(realPath); if (!dir.exists()) { dir.mkdirs(); } try { file.transferTo(new File(dir, fileName)); } catch (IOException e) { e.printStackTrace(); return Result.error(上传失败); } return Result.ok(/upload/ fileName); }两个关键点。第一getRealPath 拿到的是 Tomcat 解压后的目录重启后如果重新解压文件会丢失如果想持久一点可以把上传目录配成绝对路径比如 D:/upload再把 /upload/** 映射到那个目录。第二图片上传后如果直接回显需要保证 SpringMVC 放行静态资源否则 标签访问 /upload/xxx.jpg 会被前端控制器拦掉这事我在 5.4 里单独说。上传接口最好再限制一下文件大小Spring 的 multipart 解析器在 XML 里配 max-upload-size超过会抛 MaxUploadSizeExceededException注意要捕获它并返回 JSON而不是让页面白屏。毕设里一般限制 5MB 以内就够用避免有人拿大文件把磁盘塞满。4.2 后台登录拦截拦截器比过滤器更可控管理端的页面和接口必须登录后才能访问。SSM 项目里常见的做法是用 SpringMVC 拦截器而不是 Servlet 过滤器原因是拦截器能拿到 Handler 对象而且天然只拦截 SpringMVC 管理的路径。先写一个登录拦截器public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object admin session.getAttribute(admin); if (admin null) { response.sendRedirect(request.getContextPath() /admin/login); return false; } return true; } }再注册进 springmvc.xmlmvc:interceptors mvc:interceptor mvc:mapping path/admin/**/ mvc:exclude-mapping path/admin/login/ mvc:bean classcom.example.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptors这里有一个容易踩的坑 mvc:exclude-mapping 只排除了一层的 /admin/login如果登录页还依赖 /admin/css/style.css 这种静态资源那些访问也会被拦截。所以要么把静态资源放在 /static/** 下并配置放行要么在 exclude-mapping 里加上 /admin/css/**。更省事的做法是登录页完全不使用样式但毕设里没必要把静态资源路径规划清楚就够了。拦截器里拿 session 判断空值后直接重定向逻辑简单直接。千万别在拦截器里写业务逻辑比如查数据库判断用户是否被禁用那会让每次请求都多一次查询不合适。4.3 报价单导出用 POI 生成 Excel设计师确认量房后需要把预算项目、单价、总价打成报价单。导出 Excel 用 Apache POI 是常见方案。写一个导出接口直接在响应流里生成文件前端触发下载RequestMapping(value /quote/export/{appointmentId}) public void exportQuote(PathVariable Integer appointmentId, HttpServletResponse response) throws IOException { Quote quote quoteService.getByAppointmentId(appointmentId); response.setContentType(application/vnd.ms-excel); response.setHeader(Content-Disposition, attachment;filenamequote_ appointmentId .xls); try (Workbook wb new HSSFWorkbook()) { Sheet sheet wb.createSheet(报价单); String[] headers {项目, 数量, 单位, 单价, 合计}; Row headRow sheet.createRow(0); for (int i 0; i headers.length; i) { headRow.createCell(i).setCellValue(headers[i]); } // 这里从 quote.getItems() 循环写数据行items 是报价明细列表 for (int i 0; i quote.getItems().size(); i) { QuoteItem item quote.getItems().get(i); Row row sheet.createRow(i 1); row.createCell(0).setCellValue(item.getName()); row.createCell(1).setCellValue(item.getCount()); row.createCell(2).setCellValue(item.getUnit()); row.createCell(3).setCellValue(item.getPrice().doubleValue()); row.createCell(4).setCellValue(item.getTotal().doubleValue()); } wb.write(response.getOutputStream()); } catch (Exception e) { e.printStackTrace(); } }这里选择 HSSFWorkbook 存成 .xls 格式是因为兼容性最稳老浏览器也不挑。响应头里的 Content-Disposition 是触发下载的关键漏了它浏览器就会直接以文本形式打开乱码。POI 导出千万别把 Workbook 的关闭写在 write 之前否则文件写不完整。使用 try-with-resources 自动关闭顺序上先 write 再关数据就不会丢。生成的文件只存在于响应流里不落盘这也是常见做法既省磁盘又不用考虑清理逻辑。5. 避坑笔记SSM 项目里最常见的 6 个翻车现场这部分是我自己的血泪经验汇总。SSM 不像 Spring Boot 开箱即用配置错一处就起不来日志还特别“委婉”。下面六条按“现象→原因→解决”写踩过一次就不会再来。5.1 日期参数一提交就报 400现象前端传“2024-05-20 14:30”后台用 Date 字段接收SpringMVC 直接回 400控制台报类型转换失败。原因SpringMVC 默认没有注入日期转换器字符串转 Date 不认识这种格式。用 DateTimeFormat 注解也经常因为格式不匹配而失效。解决如果只有预约时间一个日期参数用 String 接收再手动 SimpleDateFormat 解析整个项目日期字段多的话定义一个 ControllerAdvice通过 InitBinder 注册自定义编辑器统一处理 yyyy-MM-dd 和 yyyy-MM-dd HH:mm 两种格式。很多同学宁愿每个 Controller 都写一遍转换器也不愿意建公共基类后面维护起来全是重复代码在项目里这种重复就是坏味道。5.2 MyBatis 的 #{} 和 ${} 混用导致 SQL 注入现象列表页有个排序下拉框传 asc 和 desc 一直正常。某天有人把排序参数改成一条带分号的拼接语句数据库直接报错或数据异常更严重的情况是表被清空。原因排序字段用了 ${} 拼进 SQL。${} 是纯字符串占位MyBatis 不预编译也不做转义用户传什么它就拼什么这等于把 SQL 注入的口子主动打开。解决值参数一律用 #{}动态列名比如排序字段必须做白名单。最稳的写法是不接收用户原始值而是接收 code在代码里 map 成固定列名。比如前端传 sorttime后端转成 create_time 再拼进去。这种做法把用户能控制的输入空间缩成几个固定选项注入面就不存在。另外数据库账号要单独建一个只具备增删改查权限的账号别让项目用 root 连库。毕设里很少有人注意这点但我见过翻车最多的就是这里。5.3 JSON 序列化出现 StackOverflow现象接口返回设计师的案例列表浏览器端收到的是 500日志里是一长串 StackOverflowError。原因实体类里存在双向关联比如 Case 里有 designer 对象Designer 里又有 List 。Jackson 序列化时 A 引用 B、B 又引用 A无限递归。解决在发生递归的实体属性上加 JsonIgnore或者在属性上用 JsonManagedReference 和 JsonBackReference 配一对。如果想省事干脆放弃对象嵌套用 VO 类只放需要展示的平铺字段DTO 只承载页面需要的数据。这是最不容易出问题的做法也是现在越来越流行的做法答辩时说起来也体面解耦。5.4 静态资源被 DispatcherServlet 拦掉现象Tomcat 启动后登录页能打开但 CSS、JS、上传的图片全部 404浏览器控制台显示资源加载失败。原因web.xml 里把 DispatcherServlet 映射到 /所有请求都进了 SpringMVC而 SpringMVC 默认没有配置静态资源映射遇到 .css、.js、图片请求直接返回 404。解决在 springmvc.xml 里加两个标签按需二选一!-- 方式一交给容器默认Servlet处理 -- mvc:default-servlet-handler/ !-- 方式二显式映射静态资源目录 -- mvc:resources mapping/css/** location/static/css// mvc:resources mapping/upload/** locationfile:D:/upload//方式一简单省事适合静态文件没有打散的情况。方式二适合 /upload 单独指向磁盘目录的场景注意 location 写成 file:D:/upload/ 时最后必须带斜杠。如果两种都配上一般不会冲突但建议只配一种排查起思路清晰。5.5 连接池连接数设得太大MySQL 直接拒绝连接现象并发不高但应用运行一段时间后突然报 Too many connections重启 Tomcat 又正常。过一段时间又出现周而复始。原因Druid 连接池最大连接数配了 200但 Service 层没有统一事务管理或者某个地方自己 new 了 SqlSession 没有关闭连接泄漏。毕设项目并发很低最大连接数配 20 完全够用配 200 是典型的高配低用加风险自担。解决连接池参数用 Druid 默认值附近maxActive 设为 20initialSize 设为 5。重点检查所有 Service 方法是否统一由 Spring 管理事务不要混用 MyBatis 原生 SqlSession。排查连接泄漏时可以在应用关闭前打印连接池活跃数如果活跃数一直不归零说明有连接没释放。5.6 Maven 打包缺失 lib 目录现象IDEA 里点绿按钮跑 Tomcat功能一切正常执行 mvn package 把 war 放到服务器上启动到一半抛 ClassNotFoundException打开 war 发现 WEB-INF/lib 目录是空的。原因IDEA 的 Run 配置用的是 Artifact它会把依赖放进自己输出的目录Maven 的 package 生命周期则按 pom 里依赖的 scope 和插件配置产出 war。两者不是一回事。如果依赖被标成 provided比如 Servlet API或插件配置有问题依赖就不会被打进 WEB-INF/lib。解决统一以 Maven 的 package 为准。打包前先 mvn clean别加 -o 离线参数确保本地仓库依赖齐全。打包后看 war 包体积SSM 项目至少几十 MB 才正常要是只有几百 KB基本就是 lib 没进去。在 pom 里把 Servlet API 这类容器提供的依赖保留 provided数据库驱动 scope 用 runtime其余默认 compile。插件用 maven-war-plugin 3.x 默认配置不要为了省 web.xml 把 failOnMissingWebXml 改为 false 又强行打包。6. 把项目从源码变成可演示系统最后一道验证清单系统做完到答辩前最怕的是演示现场翻车。按下面的清单走一遍能避免大多数临时状况。6.1 部署前必须检查的六个文件第一是数据库脚本确认 ddl.sql 能在干净的 MySQL 上完整执行不存在重复建表报错。第二是 jdbc.properties数据库地址、用户名、密码必须和当前环境一致尤其是密码里有特殊字符时要注意转义。第三是 springmvc.xml注解扫描的包名必须和实际包路径一致不一致会报 Controller 找不到。第四是 applicationContext.xmlService 和 Mapper 的扫描包名同样要对上。第五是 web.xmlSpring 容器监听器、DispatcherServlet 的 url-pattern、字符编码过滤器这三项缺一不可。第六是 pom.xml确认依赖没有标错 scope特别是数据库驱动和 Servlet API 这两类。检查时我习惯用 IDEA 的 Diagram 看依赖冲突。如果 pom 里同时出现两个版本的 spring-webmvc运行时最操蛋。先把六份文件过一遍再启动 Tomcat看日志里有没有 “Bean named xxx is not defined” 这类关键字有就说明扫描路径错了直接改配置而不是在代码里加 Component 硬凑。6.2 演示前按表打勾数据比代码更重要。数据库里至少要有 2 个设计师、5 个案例、3 条不同状态的预约单。演示时没有数据就等于给老师看空壳。把验证点做成表格演示前按行打勾验证点操作路径预期结果业主注册登录首页 → 注册 → 登录跳转首页显示登录名案例列表筛选案例页选“现代风格”只显示该风格案例发起预约案例详情 → 立即预约生成待受理预约单设计师接单后台 → 预约管理 → 确认状态变为已确认上传封面图后台 → 案例管理 → 上传列表显示新图导出报价单报价单 → 导出 Excel下载 .xls 且可打开我自己的教训是演示前一定先跑一遍“退出登录再登录”的完整流程。很多同学页面停留在已登录状态演示时一刷新才发现 session 失效一脸尴尬。把上面六行表格验证完你的系统就是完整可交付的。希望帮到你做毕设或练手项目最重要的是自己能把每一行配置讲明白能讲明白的代码才是你的东西。本文还有配套的精品资源点击获取
返回列表