crm经典总结

crm-01

1、搭建开发环境
	* 配置eclipse工作区字符集:UTF-8
	* 新建一个web项目(生成web.xml文件),我们这里使用的是Servlet3.1版本。
	* 将原型拷贝到WebContent目录下。
	* 部署项目,启动服务器,测试。
	* 引入jar包,配置文件,工具类。

crm-02

1、设计数据库表,工具:PowerDesigner(PDM建模:物理数据模型)
	tbl_dic_type
	tbl_dic_value
	
	数据库表设计的时候注意事项:
	
		* 一般情况下为了开发方便,能用字符串类型尽量用字符串类型:Varchar/Char....(java:String)
		
		* 在实际开发中为了保证程序的执行效率,一般是不建议添加外键约束的(FK一般不添加)。
		添加了外键约束之后,会让检索效率降低。例如A表使用了B表的某个字段作为外键,在进行A
		表数据检索的时候,不管是否取B表中的数据,总会关联去B表中进行检索,这样效率很低,为了
		提高程序的执行效率,不添加外键约束,不添加外键约束,怎么保证数据的有效性和完整性呢?
			在前端页面设计的时候,使用下拉列表方式。
	
	执行sql脚本:
		mysql> source D:\course\14-CRM\01-基础数据\crm-1.sql
		
	在实际开发中,sql脚本文件可能很大,比如:64MB(sql脚本文件的大小)
	记事本以及普通的文本编辑器,包括navicat都是无法打开的,这种文件需要使用source命令来执行脚本。
	
2、把字典类型和字典值模块的html修改为jsp,解决所有的404错误。
	* html修改为jsp的顺序:
		先打开html,添加<%@page contentType="text/html;charset=UTF-8"%>
		再重命名。
		<base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
		base标签设置了base路径之后,当前页面自动从项目的根路径下开始查找资源。
		
	* 添加base标签
		<base>标签属于html的语法,不属于JSP的语法。
		<base>标签用来设置当前页面的基础路径,当前页面中凡是不以“/”开始的路径都会自动添加base。
		当前页面中以“/”开始的路径和base没有关系。
		
	* 将所有jsp中的../全部替换成空白。
	
	* 解决404

3、实现保存字典类型。

	3.1、字典类型表单验证。(以下描述你们将在“详细设计文档”中看到。)
		编码不能为空(前端验证)
		编码只能由数字和字母组成(前端验证)
		编码在数据库表当中是主键,要求具有唯一性(后台验证:ajax)
		失去焦点则验证
		验证消息显示在控件的下方
		消息要求红色字体,12号。
		获得焦点之后,自动清除错误信息,并且文本框中的内容不合法的时候,文本框的内容自动清空。
		用户最终保存按钮点击之后,必须保证所有的表单项都是合法的方可提交。
	

注意:使用javascript代码怎么弹出modal,怎么隐藏modal?
	// 显示modal窗口
	$("#modalId").modal("show");
	// 隐藏modal窗口
	$("#modalId").modal("hide");

4、使用jquery发送ajax请求:
	第一种方式:通用的方式,比较灵活 
		$.ajax({
			type : "get/post",			请求的方式
			url : "url",				url
			data : {}或者"",				请求发送的数据(当请求发送的数据是字符串的时候,需要满足什么格式?name=value&name=value&name=value)
			beforeSend : function(){	ajax请求发送之前的回调函数
				// return true;  // 表示继续发送该ajax请求。 
				// return false; // 表示放弃发送该ajax请求。
				
				if(表单在最后提交的时候,验证所有的表单项,当所有的表单项都合法的时候){
					return  true;
				}
				return false;
			},
			success : function(json){
				// 响应正常结束之后的回调函数。
				// 通常在这里解析json,刷新前端数据。
			},
			dataType : "",  // 用来指定响应的数据类型
			async : false,  // 设置ajax请求为同步方式(默认是异步方式,异步是并发,同步是排队。)
			cache : false,  // 解决get请求的缓存问题。自动在请求url后面添加时间戳。保证每一次请求路径不一样,这样浏览器就不会走缓存了。
			.....
		});
	第二种方式:专门发送post,基于$.ajax()实现的
		$.post(url,data,function(json){},dataType);
	
	第三种方式:专门发送get,基于$.ajax()实现的
		$.get(url,data+时间戳,function(json){},dataType);
	
	第四种方式:发送get ajax请求,并且一定返回json
		$.getJSON(url,data,function(json){});
	
	....
	
5、get请求和post请求如何选择?

	post(邮寄,邮递,传送):
		- 请求发送的数据当中有敏感信息
		- 请求参数过大
		- 请求发送的不是普通字符串,例如:文件、图片、视频等
		- 主要目的是为了向服务器传送数据,而不是获取数据时采用post
		
	get(获取):
		- get请求的最终响应结果会被浏览器自动cache。(怎么避免get缓存,时间戳)
		- get请求发送的数据在请求行的url后面,所以最终会显示到浏览器的地址栏上。
		- get请求只能发送普通字符串。
		- get请求不能提交大量的数据。
		- get请求最终的目的是为了从服务器端获取资源,到浏览器上展示数据。
	
	增 删 改一般都属于post。
	查 属于get。
	
	http协议中这样规定:
		post请求适合于浏览器客户端向服务器传送数据。专门负责传送数据的。
		get请求适合于从服务器端索取数据到浏览器上,并且显示数据。所以get请求应该是设计为支持缓存的。
6、包名:
	com.wkcto.crm.settings.web.controller
	公司域名倒序 + 项目名 + 模块名 + 功能名
	
	一个模块对应一个Controller
	一个业务对应一个service
	一张表对应一个dao

crm-03

1、保存字典类型。
	* 保存之前必须验证所有的表单项是合法的。
	* 这里的保存只要提交form表单就行。可以不用ajax。

2、重点:什么时候ajax使用同步方式?
	在表单验证的时候,其中某个或者某些表单项需要ajax验证,那么要求这些ajax请求必须是同步请求。

crm-04

1、添加字符集过滤器,保证post请求不出现乱码。

crm-05

1、保存字典值:

	1.1、用户点击“创建”按钮,跳转到save.jsp页面,字典类型编码动态显示。
		c:forEach
		
	1.2、表单验证:
		字典类型编码不能为空
		字典值不能为空
		在“同一个字典类型编码”下“字典值”具有唯一性。
		排序号可以为空,但不为空的时候必须是正整数。

		先把每一项的name和id全部加上。
		添加三个span。

crm-06

1、封装一个响应json的工具类。

crm-07

1、对部门表进行设计。

2、将部门模块的html修改为jsp,解决404错误。

3、发送ajax post请求,保存部门。

注意:
	重置表单之后,再弹出modal窗口。

crm-08

1、将用户维护模块相关的html修改为jsp,解决404错误。
2、弹出“保存用户”的modal窗口。
	当用户点击“创建”按钮的时候,发送ajax get请求,取出所有的部门,解析json,拼接html。
	var html = "";
	$.each(array , function(i , n){
		html += "";
	});
	$("#").html(html);
3、设计数据库表。
	tbl_user
		特殊字段:
			部门编号(外键)
			失效时间
			允许访问的IP
			锁定状态
			登录密码(MD5)
			创建人和修改人(登录账号)
			
4、用户点击保存按钮的时候,发送ajax post请求,提交用户信息,保存用户。

5、“自学”了一个bootstrap datetimepicker插件。(日历控件)

crm-09

1、实现用户登录功能:(使用ajax post方式,进行登录。)
	* 将login.html修改为jsp,解决404错误。
	* 登录页面加载完毕之后,用户名文本框自动获得焦点。
	* 用户点击登录按钮登录,回车同样可以登录。
	* 登录请求发送之前,必须保证用户名和密码不能为空。

crm-10

1、十天内免登录

	* 用户登录的时候选择了“十天内免登录”,登录成功之后,关闭浏览器,在十天之内,使用该版本的浏览器打开crm项目,出现在
	用户浏览器页面上的是“工作台”。
	
	* 怎么实现十天内免登录?
		使用Cookie机制。
		登录成功之后,假设用户选择了十天内免登录,那么应该创建十天有效的Cookie,将用户的登录信息存储到Cookie当中,将
		Cookie发送给浏览器,浏览器将Cookie保存在硬盘文件当中,只要不清除Cookie,只要还是当前版本的浏览器,下一次再
		访问该web站点的时候,浏览器自动提交Cookie给服务器,服务器从Cookie中获取用户的登录信息,验证信息通过之后,直接
		跳转到“工作台”。
	
	* 回顾Cookie:
		- Cookie只有在javaweb中有吗,其他语言的web开发中有Cookie吗?
			有,并且Cookie不属于java,属于HTTP协议的一部分。只要是web项目,只要是B/S架构,一定离不开:cookie 和 session。
		- Cookie和Session存在的目的是什么?
			是为了补充http协议的不足,HTTP协议的不足是什么?
				http协议是无连接的无状态的协议。
			我们可以使用Cookie和Session来保存会话的状态。
			其中Cookie可以将会话的状态保存在浏览器客户端上。
			其中Session可以将会话的状态保存在服务器端上。
		- Session实现机制中是依赖Cookie的:
			服务器端的session列表当中的每一个session对象都有sessionId,而SessionId是存储在Cookie当中的。
			sessionId最后存储在Cookie中,Cookie放在浏览器的缓存当中,只要不关闭浏览器,下一次访问会自动提交
			带有SessionId的Cookie给服务器,服务器接收该sessionId用来查找对应的Session对象。
		- HTTP为什么是无连接无状态协议?
			这是为了降低服务器的压力。只有用户请求的过程中是连接的,响应结束之后,浏览器与服务器之间的连接就断开了。
		- 由于HTTP协议是一种无连接无状态协议,所以关闭浏览器的时候,服务器是不知道的,那么Session对象也不能直接
		给“客户”保留着,毕竟Session对象也会占用JVM的内存空间,所以在Servlet规范中,使用Session超时机制来销毁
		Session对象。默认的配置是:30分钟,常见的MIS系统一般设置为120分钟,安全级别较高的系统,时间会很短,例如,
		网银系统一般是用户在3分钟之内没有再次操作网银页面,网银系统会自动销毁Session对象。
		
		- Cookie是怎么保存会话状态的,经典的案例有哪些?
		
			* 购物车的实现:
				没登录的情况下,向购物车添加商品,实际上是将商品的编号放到了Cookie当中,Cookie被保存在浏览器客户端
				硬盘文件中,下一次访问该购物网站的时候,自动读取关联的Cookie,获取商品编号,初始化购物车。这种会话
				状态的保存是不稳定的,当清除Cookie之后,会话状态丢失。
				
				在登录的情况下呢:向购物车当中添加商品,实际上这个商品信息关联客户的信息,一并存储到数据库表当中。这种
				会话状态的保留是稳定的。不会因为更换浏览器版本而丢失。
				
			* 十天内免登录。
		
		- Cookie禁用之后,一些web站点的功能就无法使用了。
		
		- 当Cookie禁用之后,怎么实现session机制呢?
			URL重写机制。
			可以在URL后面添加:jsessionid..
				url;jsessionid=32位长度的字符串?name=value&name=value&name=value....
			但这种方式会大大提高编程复杂度,每一个URL后面都需要动态的添加jsessionid
			所以99%的web站点都会提示你开启浏览器Cookie功能。
		
		- 在servlet中Cookie的API?
			
			* 创建Cookie
				Cookie cookie = new Cookie(String name,String value);
			* 设置有效时长
				cookie.setMaxAge(秒);
				秒 > 0	存储到硬盘文件
				秒 = 0	删除该Cookie
				秒 < 0	不被存储
				不设置有效时长,默认是:存储到浏览器缓存当中,直到浏览器关闭之后销毁Cookie。
			* 设置Cookie的关联路径
				cookie.setPath("/crm");
				以上代码的含义表示:以后浏览器只有发送/crm请求的时候,该Cookie就会被自动发送给服务器。
			* 获取Cookie的name和value
				String name = cookie.getName();
				String value = cookie.getValue();
			* 将Cookie发送到浏览器客户端
				response.addCookie(cookie);
			* 服务器获取浏览器客户端提交的Cookie 
				Cookie[] cookies = request.getCookies();

crm-11

1、常量类:

	项目中多次出现:request.getSession().setAttribute("user", user);
	其中"user"这个字符串多次编写,每一次程序员编写这个"user"字符串的时候都需要很谨慎的编写。
	因为在编写过程中,当错误的时候,编译器也不会提示错误信息。
	
	其实,我们开发有一个原则:错误越早发现越好,尽可能让所有的错误发生在编译阶段。
	编译器能够检测出来的错误尽可能交给编译器来完成。

2、登录成功之后,在工作台右上角显示当前登录的用户。
	workbench/index.html修改为jsp,然后在jsp中使用EL表达式。
	${sessionScope.user.name}
	${user.name}

3、登录拦截过滤器:
	拦截什么?
		*.jsp
		*.do
	什么不能拦截?
		/login.jsp
		/login.do	
		/welcome.do
		
		用户已经登录则不需要拦截(登录的标志:session中有user)	

crm-12

1、将市场活动相关html修改为jsp,解决404错误。
2、设计数据库表:市场活动模块相关的表。
	tbl_activity
	tbl_activity_remark
3、弹出创建市场活动的modal窗口。
	- 所有者动态(动态的下拉列表)
		* select * from tbl_user where loginAct != 'admin' (系统账号超级管理员admin一般是内置账号,前端显示的所有者不应该显示该账号。)
		* 关于下拉列表定位的问题:用户要求,张三登录,则所有者默认选择张三。
			想让下拉列表的某个选项选中,可以通过设置下拉列表的“value”即可。
			设置下拉列表的value是zhangsan,则zhangsan会被自动选中。
	- 开始日期和结束日期添加日历控件。
4、保存市场活动。
	用户点击保存按钮,发送ajax post,保存市场活动信息。

crm-13

1、实现分页查询的后台功能。(先不实现前端的)

crm-14

1、市场活动列表页面加载完毕之后,显示第一页数据。

crm-15

1、给分页查询添加翻页功能:
	翻页使用bootstrap pagination插件来完成。
	
2、用户点击查询按钮,分页查询。

3、翻页的时候条件是怎么处理的?
	隐藏域 hidden
	点击“查询”的时候,将四个条件放到隐藏域当中。
	每一次分页查询的时候,条件都从隐藏域当中取。
	每一次翻页的时候,再将隐藏域当中的条件更新到页面的文本框中。

4、分页查询面试官都会问哪些问题呢?
	* 分页查询你是怎么实现的,说一下?
		重点要说的是:
			- mybatis的动态sql(where if)
			- sql语句的limit
			- 封装了一个分页查询专用的VO对象(PaginationVO)
			- 最后捎带着说一下,前端使用了一个分页的组件:pagination
	* 翻页的时候查询条件是如何处理的?
		隐藏域。

crm-16

1、当前CRM项目当中有很多位置都需要分页查询,目前service类当中的Map集合用来存储展示的数据:
	Map<String, Object> pageMap = new HashMap<>();
	pageMap.put("total", activityDao.getTotalByCondition(conditionMap));
	pageMap.put("dataList", activityDao.getDataListByCondition(conditionMap));

	以上由于功能不止一个,建议使用一个专门的类进行封装,所有的分页查询功能都使用这个封装的类,
	该类由于存储了前端展示的数据,所以这个类我们成为VO(View Object:视图对象)
	VO 、 DAO等都属于JavaEE设计模式。
	
	VO对象中的数据将来最终是负责向前端浏览器展示数据的。
	
	DTO
	PO
	POJO
	BO
	.....

crm-17

1、复选框的全选和取消全选

	$("#firstChk").click(function(){
		$(":checkbox[name='id']").prop("checked" , this.checked);
	});
	
	$("#activityTbody").on("click" , ":checkbox[name='id']" , function(){
		$("#firstChk").prop("checked" , $(":checkbox[name='id']").size() == $(":checkbox[name='id']:checked").size());
	});

2、删除市场活动。
	* 删除之前要提示用户是否确认删除。
	* 一次可以删除多个市场活动。

crm-18

1、修改市场活动。
	* 第一步:弹出修改市场活动的modal窗口。
	* 第二步:用户点击修改按钮,发送ajax post请求。在哪一页修改还回到那一页。

crm-19

1、展示市场活动的明细。
	detail.jsp

crm-20

1、备注列表先修改一下样式。
	鼠标移动到div当中,备注的修改和删除按钮显示为红色。

2、市场活动的明细页面加载完毕之后,展示该市场活动的备注列表。
	页面加载完毕之后发送ajax get请求,根据市场活动id获取备注列表。

crm-21

1、保存市场活动备注。
	* 保存市场活动的时候备注信息不能为空。
	* 保存成功之后,清除文本域中的“备注信息”。

crm-22

1、删除备注。

crm-23

1、修改备注。
	* 弹出修改备注的modal窗口。

注意目的:设置id,id重复了添加前缀。

crm-24

1、关于市场活动的导入和导出。

	* 这里所描述的导入和导出指的是excel。	
		导出:将数据库表当中的数据导出到excel文件当中。
		导入:将excel文件中的数据导入到数据库表当中。
		
	* 我们这里先来看一下导出市场活动。
		在java开发中要实现excel的导出,可以使用java当中的一个开源组件:apache POI
	
	* 在使用POI组件之前,我们最好要对excel文件结构有一定的认识。
	
		一个excel文件的组成是怎样的呢?
			excel文件:WorkBook 工作簿
			一个WorkBook工作簿当中可以容纳多个:Sheet 表格
			一个Sheet表格当中有多个行:Row 行
			一个Row行上面有多个单元格:Cell 单元格
			
			WorkBook --> Sheet --> Row --> Cell
			apache POI对excel文件的操作主要使用的是以上的几个对象(因为java语言本身就是完全面向对象的。)
	
	* 我们这里先实现excel的导出功能,导出excel:使用POI写excel文件。
		我们这里采用网络搜索的方式,搜索相关的资料或者博客,找到简单的示例,先运行起来,然后在简单程序的基础之上
		进一步学习,然后改造程序,最终达到效果。(这个过程不容易的。)
			
2、市场活动的导出:
	提供两个操作:一个是导出选中,另一个是导出全部。

crm-25

1、导入市场活动。(先不使用异步的方式,使用传统方式导入:提交form表单的方式。)
	第一步:文件上传。(apache commons fileupload)
	第二步:读excel。(POI)

crm-26

1、导入市场活动。(采用异步ajax方式先完成文件上传,然后再导入数据。)
	$.ajax({
		...
		contentType : false,   // 设置encType为multipart/form-data
		processData : false,   // 设置以对象的形式提交数据(提交文件)
		data : new FormData().append("myfile" , $("#activityFile")[0].files[0]),
		...
	});

crm-27

1、将剩下的所有页面修改为jsp,解决404错误。

2、将tbl_dic_type和tbl_dic_value数据通过执行sql脚本的方式初始化数据。

3、思考:系统当中有很多位置需要下拉列表,每一次需要下拉列表都要从数据库中查询吗?有没有什么好的方案?
	下拉列表中的数据有这样的几个特点:
		第一:数据量较小
		第二:所有用户共享
		第三:一般不会轻易的修改
	当满足以上的三个条件的时候,可以考虑将其存储到application域当中。
	application域可以看做是一个缓存:cache。
	使用缓存机制,减少IO操作,是提高程序执行效率的重要手段。
	在实际开发中,遇到优化瓶颈的时候,首先要想到缓存机制。
	
	将字典值查询出来,存储到一个什么样数据结构当中,application域当中存储什么?
		application.setAttribute("appellationList" , appellationList);
		application.setAttribute("clueStateList" , clueStateList);
		.....
		
		<c:forEach items="${appellationList}" var="字典值对象">
			<option value="${字典值对象.value}">${字典值对象.text}</option>
		</c:forEach>
		
	写一个监听器,在服务器启动阶段,调用service返回一个Map集合,然后遍历Map集合,将数据存储到application域当中。
		Map<String,List<DicValue>>
		key					value
		----------------------------------------
		"appellationList"	appellationList
		"clueStateList"		clueStateList
		....
4、将“线索、客户、联系人、交易”模块的所有表设计出来。
	线索模块
		tbl_clue		线索表(pk)
		tbl_clue_remark	线索备注表(pk + fk[clueId])
		tbl_clue_activity_relation 线索和市场活动关系表(pk + fk[clueId] + fk[activityId])
	客户模块
		tbl_customer	客户表(pk)
		tbl_customer_remark 客户备注表(pk + fk[customerId])
	联系人模块
		tbl_contacts	联系人表(pk + fk[customerId])
		tbl_contacts_remark 联系人备注表(pk + fk[contactsId])
		tbl_contacts_activity_relation 联系人和市场活动关系表(pk + fk[contactsId] + fk[activityId])
	交易模块
		tbl_tran		交易表(pk + fk[customerId] + fk[activityId] + fk[contactsId])
		tbl_tran_remark	交易备注表(pk + fk[tranId])
		tbl_tran_history 交易历史表(pk + fk[tranId])

crm-28

1、弹出创建线索的modal窗口:
	* 所有者动态(下拉列表定位)
	* 线索来源和线索状态下拉列表从application域当中取出forEach
	* 下次联系时间日历控件。 (设置控件的显示位置:pickerPosition : "top-right")
	
2、发送ajax post请求保存线索。

crm-29

1、查看线索的明细。

crm-30

1、展示该线索关联的市场活动列表。
	前提是知道:线索id(clueId=77544dcfc4774e70a95ac1ba32912a92)
	tbl_clue_activity_relation car
	tbl_activity a
	tbl_user u
	
	select
		a.name, a.startDate, a.endDate, u.name as owner, car.id as relationId
	from
		tbl_activity a
	join
		tbl_clue_activity_relation car
	on
		car.activityId = a.id
	join
		tbl_user u
	on
		a.owner = u.id
	where
		car.clueId = #{clueId}

crm-31

1、解除关联。
	* 发送ajax post请求,提交关系id,删除数据库中的数据,前端删除一个tr。

crm-32

1、当前线索关联市场活动:

	1.1、弹出关联市场活动的modal窗口:
		* 清空查询条件
		* 第一个复选框取消选中
		* 清空tbody
	
	1.2、根据市场活动名称查询市场活动。支持模糊查询!
		回车键的时候查。(keyCode==13)
		bootstrap当中的modal窗口只要接收到回车键,则modal窗口自动关闭。
		JS中的事件会冒泡,作用到子对象上的事件,会继续传递给父对象,没有阻止会一直传递下去。
		
		怎么防止js的事件冒泡?
		
			方式一:event.stopPropagation();
			$("#div1").mousedown(function(event){
				event.stopPropagation();
			});
			
			方式二:return false;
			$("#div1").mousedown(function(event){
				return false;
			});
		
		这里防止业务上出现漏洞?已关联过的市场活动不能查出来。(sql语句子查询。)
			<select id="getByName" resultType="Activity">
				select
					a.id, a.name, a.startDate, a.endDate, u.name as owner
				from
					tbl_activity a
				join
					tbl_user u
				on
					a.owner = u.id
				where
					a.name like '%' #{arg0} '%'
				and
					a.id not in (select activityId from tbl_clue_activity_relation where clueId = #{arg1})
			</select>
			
	1.3、删除市场活动的时候应该级联删除对应的备注信息。

crm-33

1、关联市场活动:
	* 全选和取消全选。
	* 用户点击关联按钮的时候:
		至少关联一个。可以一次关联多个。
		关联实际上就是往关系表插入数据:
			tbl_clue_activity_relation
			id			clueId			activityId
			----------------------------------------------
			UUID		前端提交			前端提交

crm-34

1、在detail.jsp页面上点击"转换"按钮跳转到convert.jsp页面:

	两个jsp直接传送数据?
	
		detail.jsp?name=value&name=value&name=value....
		
		convert.jsp中怎么取数据{
			${param.name}
			${param.name}
			${param.name}
			${param.name}
			${param.name}
			${param.name}
			${param.name}
			${param.name}
			${param.name}
		}			
		
		重点:EL表达式当中有一个隐含对象,叫做:param
		用来代替:
			<%=request.getParameter("name")%>
			
2、分析:转换涉及到哪些表?
	tbl_clue
	tbl_clue_activity_relation
	tbl_clue_remark
	
	tbl_customer
	tbl_customer_remark
	
	tbl_contacts
	tbl_contacts_activity_relation
	tbl_contacts_remark
	
	
	tbl_tran
	tbl_tran_history
	tbl_tran_remark

3、分析:转换的实现步骤(共11张表)?(线索id已知)-----> 目前我们先不考虑交易。
	1、根据线索id查询线索信息。
	2、从线索对象中提取客户信息,保存客户信息(判断该客户的信息是否已经存在:根据公司名称查询,精确匹配)。
	3、从线索对象中提取联系人信息,保存联系人信息。(这里不考虑去除重复记录,直接保存)
	4、将“线索和市场活动的关系”转换到“联系人和市场活动的关系”
	5、将“线索备注”转换到“客户备注”、“联系人备注”
	6、删除线索对应的备注
	7、删除线索和市场活动的关系
	8、删除线索

4、注意事务,必须同时成功或者同时失败,也就是说必须只能调用一次service方法。

crm-35

1、线索转换的时候用户选择了创建交易:
	* 保存交易
	* 保存交易历史
	* 保存交易备注(线索备注有的话,可以转换成交易备注。)

crm-36

1、展示创建交易的页面:
	* 预计成交日期和下次联系时间(日历控件)
	* “阶段”动态显示
	* “类型”动态显示
	* “来源”动态显示
	* “市场活动源”和“联系人名称”固定(不再实现动态效果)
	* 所有者动态显示,并且定位下拉列表选项。
	
2、下拉列表定位:
	第一种方式:页面加载完毕之后定位。
		$(function(){
			$("#owner").val("${user.id}");
		});
	第二种方式:EL表达式当中使用三目运算符。
		${userObj.id eq user.id ? "selected" : ""}
	
	注意:
		第一:EL表达式是支持三目运算符的。
		第二:eq是EL表达式当中的运算符,底层实际上调用了java语言中的equals方法。
	另外:
		${"user"}和${user}有什么区别?
			${"user"} 表示把"user"当做一个普通的字符串直接打印输出到浏览器。
			${user} 表示从四个域当中检索数据,找到数据之后输出到浏览器。
			
3、选择不同的阶段,生成不同的可能性。

	* change事件。
	
	* “阶段”对应的“可能性”,他们之间的对应关系应该在配置文件中体现出来:
		这里选择属性配置文件。
		Stage2Possibility.properties 
			Stage To Possibility:阶段对应的可能性。
		log4j : logger for Java
		dom4j : dom for Java
		
		注意:属性资源文件中不能直接存储中文,高版本的eclipse自动将中文转换成unicode码。
		那么低版本的eclipse怎么办?程序员可以手动转换:native2ascii.exe
	
	* 在服务器启动阶段:SystemInitListener:
		读取Stage2Possibility.properties,将读取到数据放到Map集合当中,将Map集合放到application域当中。

crm-37

1、客户名称支持自动补全。
	bootstrap typeahead

crm-38

1、保存交易。
	提交form表单。

crm-39

1、查看交易的明细:
	五张表连接。

crm-40

1、查看交易的历史列表。
	页面加载完毕之后发送ajax get请求,根据交易id获取交易历史列表。

crm-41

1、展示阶段图标。

	* 修改页面:失败的图标都以“叉号”显示。
	
	* 实现什么样的效果?
	
		第一种情况:当前阶段处于“正常期”:
			当前阶段的图标则:glyphicon glyphicon-map-marker (颜色:绿色)
			当前阶段之前的图标则:glyphicon glyphicon-ok-circle(颜色:绿色)
			当前阶段之后的图标则:glyphicon glyphicon-record(颜色:黑色)
			失败的图标则:glyphicon glyphicon-remove-circle(颜色:黑色)
			
		第二种情况:当前阶段处于“失败期”:
			当前失败的阶段图标则:glyphicon glyphicon-remove-circle(颜色:红色)
			其它失败的阶段图标则:glyphicon glyphicon-remove-circle(颜色:黑色)
			其它正常的图标则:glyphicon glyphicon-record(颜色:黑色)
		
		怎么判定当前阶段是失败期还是正常期?
			主要依据是可能性是否为0

crm-42

1、点击图标,更新当前交易的阶段。(这个版本不实现图标的更新。)
	* 提交什么?
		tranId
		stage
		money
		expectedDate
	* 返回什么?
		{"stage":"","possibility":"","editTime":"","editBy":""}

crm-43

1、更新图标。
	窍门:使用下标i作为每一个图标的id。

crm-44

1、统计图表:

	实现交易的统计。(使用百度的ECharts完成统计图表的展示。)
	
	对什么进行统计呢?
		对“每个阶段”的“交易数量”进行统计。
		共100笔交易:
			50笔成交
			10笔因竞争丢失而关闭
			20笔01资质审查
			10笔02需求分析
			.....
			
	客户可能会按照月进行统计,也可能是按照季度进行统计,还可能按照年统计。
	也可能统计所有数据(自公司成立以来!)
	
	这个统计图表我们准备使用:漏斗图。
	
	统计图表非常多:
		柱状图
		饼状图
		漏斗图
		曲线图
		....
			
2、扫尾:

	* 自己实现发送邮件功能。(javamail)
	
	* 系统当中有很多位置都有“下次联系时间”:
		当“下次联系时间”到了之后,服务器端应该自动推送消息给浏览器,提示业务员。
		推送技术可以使用:pushlets
		反向AJAX。
		CRM项目中有没有实时性效果的技术呢?
			有,下次联系时间到了之后,服务器自动推送信息,推送信息使用的是:pushlets
		
	* CRM项目中还有权限管理模块:
		权限管理模块使用的是:apache shiro框架实现的(shiro框架底层是基于RBAC:Role Based Access Control:基于角色的访问控制。)
		面试官问:CRM中有权限吗?
			有,当时我们好像使用的是apache shiro。但这块不是我负责的。
		除了shiro之外还有其它的,例如spring框架下的子框架:spring security
	
	* 面试官可能还会问,CRM项目中有没有“流程管理的功能”/“工作流”相关的内容?
		有,我们使用的是Activiti框架实现的工作流。
		
		jBPM5与Activiti5都是工作流框架:
			jBPM5老
			Activiti5新(使用这个比较多)
		
		流程管理是一个非常通用独立的模块。例如:学生请假、报销差旅费....
	
3、面试之前一定要将CRM项目的核心业务记忆一下。

4、使用单元测试工具junit4进行单元测试。

猜你喜欢

转载自blog.csdn.net/qq_30347133/article/details/84105819
CRM