Spring+SpringMVC+MyBatis+JQ+BootStrap练习

CRM系统:Customer Relationship Management 客户关系管理系统

下面写的关于try{}catch(){}有很多错误地方 在没有SpringMVC全局异常处理下

 功能:基本的增删改查,文件上传下载,Excel表格等

技术架构:

视图层(view):展示数据,与用户交互.

        html:静态页面,css:美化页面,js动态显示页面,jsp其实就是servlet但是因可以动态交互页面            传值你说属于前端就属于前端吧

控制器(Controller):控制业务处理流程(接收请求,接收参数(封装参数,根据不同的请求调用对应          业务层处理,根据处理结果返回响应信息))

        servlet(功能可以实现但是很麻烦,一个请求对应一个servlet),SpringMVC用一个共有的                  servlet(DispatcherServlet)来处理所有请求.类似SpringMVC技术的还有                                          如:webwork,struts1,struts2

业务层(Service):处理业务逻辑.  工作流:activiti,JBPM

(临时想起以前项目中一个报错,大概原因是:当时在一个将定时任务方法@Scheduled(cron="xxx")

和另开线程的方法@Async写在一个类中,不能写一个类原因??)

Spring默认用的JDK代理

持久层(Dao/Mapper):操作数据库.

                jdbc,mybatis对jdbc进行封装优化 差不多的技术(hibernate),ibatis,可以说                      Mybatis是ibatis的升级版本

整合层:维护类资源,维护数据库资源.Spring(通过DI(依赖注入实现IOC控制反转),AOPO)

与Spring相似功能以前ejb,corba 而Spring整合了大部分常用技术,Spring全家桶,一统江湖

分析与设计:

1.架构设计:

        1.1)物理架构设计:

                    应用服务器:tomcat,weblogic,liberty,websphere,jboss,resin(微软平台)等

                    数据库服务器:mysql,oracle,DB2,sqlServer(教学用)

         1.2)逻辑架构设计:代码分层

                     视图层-->控制层-->业务层-->持久层-->数据库

          1.3)技术选型:Java

2.项目设计:

         2.1)物理模型设计:哪些表,字段,类型,长度,表之间关系

        设计软件:powerdesigner------xxx.pdm文件

         2.2)逻辑模型设计:哪些类,哪些属性和方法,方法的参数和返回值,类和类之间的关系

         设计软件rational rose-----xxx.pdl文件    UML:建模工具

         2.3)界面设计(美工):企业级应用:朴素   互联网应用:炫酷

         2.4)算法设计:

搭建开发环境:创建项目,添加jar包,添加配置文件,静态页面,添加公共类,能够正常启动。

编码实现

核心业务:

        系统管理功能:保证系统,业务管理安全的功能正常运行.如用户登录,安全退出,登录验证

        菜单,权限部门等等

        业务管理功能:处理具体业务数据

CRM大致表结构:

tbl_user 用户表

tbl_dic_type   数据字典类型表

tbl_dic_value  数据字典值

tbl_activity  市场活动表

tbl_activity_remark 市场活动备注表

tbl_clue 线索表

tbl_clue_remark 线索备注表

tbl_clue_activity_relation 线索和市场活动关联关系表(中间表)

tbl_customer 客户表

tbl_customer_remark 客户备注表

tbl_contacks  联系人表

tbl_contacts_remark 联系人备注表

tbl_contacts_activity_relation   联系人和市场活动关系表(中间表)

tbl_tran  交易表

tbl_tran_remark   交易备注表

tbl_tran_history   交易历史表

1.主键字段:在数据库表中,如果有一组字段能够唯一确定一条记录,则可以把他们设计成表的主键

推荐使用一个字段做主键,而且推荐使用没有业务含义的字段做主键,比如:id等.

主键字段的类型和长度由主键值的生成放还是来决定:

        主键值的生成方式:l

        1)自增:借助数据库自身主键生成机制

                        数值型,长度由数据量来决定

                        运行效率低,开发效率高

        2)assighed:程序员手动生成主键值.唯一非空,算法

                        hi/low:高低算法,数值型,长度由数据量决定

                        UUID:字符串  长度32位

        3)共享主键:比如说身份证号主键回传,再将该主键添加到驾照表作为主键(不常用)

        4)联合主键:由多个字段的类型和长度决定(不推荐,不方便使用)

2.外键字段:      用来确定表与表之间关系.

        现实世界不是孤立存在的,任何两个事物必然存在某种关系

        1)一对多:一个表中的一条记录对应另外一张表的多条记录

                        父表---------子表

                下面的例子  class_id就是外键

                tbl_class                                          tbl_student

                id            name                          id      name      class_id

                111           class1                      1001     zs         111                       

                222          class2                      1002      ls          222                     

                333          class3                      1003     ww        333

                添加数据时,先添加父表记录,再添加子表记录

                删除数据时,先删除子表数据,再删父表记录

                查询数据时,可能会进行关联查询:

                内连接:查询所有符合条件记录,要求结果在两张表中都有相对应的记录

                外连接:分左外连接和右外连接.

                比如左外连接:查询左侧表中所有符合条件的数据,即使在右侧表中没有相对应的数据,以                  左侧表为主

                //查询所有姓张学生的id,name,和所在班级名.这个需求在内连接,和外连接的实际运用时,比如说,要查询所有姓张的同学,即使还没有分配班级.  那么以学生表为主用左外连接班级表,

如果要查寻姓张的同学,要求一定要有班级信息,那么用内连接.

               如果外键不能为空,那查询时候用内连接或者外连接都一样,因内连接效率高,因此优先用内连接,如果外键可以为空,那么就看具体情况,比如只需要查询那些在另一张表中有相对应的记录,使用内连接,假如需要查询左侧表中所有符合条件的记录,使用左外连接.

//假如外键不能为空

select s.id,s.name,c,name cname from tbl_student s join tab_class c on s.class_id=c.id where s.name like '张%';

sql执行

-----------select 

-----------from

-----------join

-----------on

-----------where

-----------group by

-----------having

-----------order by

-----------limit

一对一:一张表中的一条记录,只能对应另一张表的一条记录,反过来也一样

                比如说公民表       护照表

                tbl_persopm                   tbl_card    (不推荐用共享主键,下面以共享主键演示   流汗)

                id            name               id                 name

                1001       zs                    1001            card1

                1002       ls                     1002            card2

这种情况,添加数据,先添加先产生的表,获得主键,也作为另一张表主键

删除数据,删除后产生的表记录,再删除先产生的表记录

查询数据:如果是共享主键,那好处也来了,比如要查张三护照表,在已知道张三id情况下就不需要关联查询  直接  select * from tbl_card where id=?    不推荐共享主键,主要原因可能是关联耦合性太强,但是感觉蛮好用的歪.

一对一的另外一种方式  加唯一外键(一般在后产生的表里加外键) 也是实际工作中常用比如

        tbl_persopm                   tbl_card     

         id            name               id         name        person_id(添加唯一约束)

        1001       zs                    111     card1       1001

        1002       ls                     112     card1       1002

为避免一对一关系被某个插入数据搞错了,比如说不小心插入了同一个person_id的两本护照

在一对一关系时通常会给外键加个唯一性约束

注意:一对一实际上就是一种特殊的一对多,只不过它的外键添加了唯一性约束,那么查询规则其实和上面一对多的规则是一样的.

多对多:一张表中的一条记录,对应另外一张表的多条记录,翻过来就是另一张表的一条记录对应这张表的多条记录

比如学生表和课表

tbl_student                              tbl_course

id     name                               id         name

1001   zs                                111        java

1002   ls                                 222       mysql

                   中间表tbl_student_course

                        student_id        course_id

                        1001                 111

                        1002                 222

                        1001                 222

添加数据时(tbl_student和tbl_course两张表作为父表先添加)再添加中间表(tbl_student_course)子表记录

删除数据时,先删除子表记录(tbl_student_course),再删除父表

查询数据时:可能会进行关联查询  

//查询所有姓张的学生的id,name,和所有课程的name

select s.id,s.name,c.name cname

from tbl_student s 

inner join tbl_student_course sc on s.id=sc..student_id

inner join tbl_course c on sc.course_id=c.id where s.name like '张%'

关于日期和时间的字段:

java:Date

mysql:date(只有年月日),time(只有时分秒),datetime(既有年月日也有时分秒)

实际工作中有可能都用字符串来处理

char(10)  yyyy-MM-dd

char(19) yyyy-MM-dd HH:mm:ss

这样就无需考虑日期转换

创建CRM数据库实例

导入sql


Target Server Type    : MYSQL
Target Server Version : 50536
File Encoding         : 65001

Date: 2018-11-27 17:02:13
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `tbl_user`
-- ----------------------------
DROP TABLE IF EXISTS `tbl_user`;
CREATE TABLE `tbl_user` (
  `id` char(32) NOT NULL COMMENT 'uuid\r\n            ',
  `login_act` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `login_pwd` varchar(255) DEFAULT NULL COMMENT '密码不能采用明文存储,采用密文,MD5加密之后的数据',
  `email` varchar(255) DEFAULT NULL,
  `expire_time` char(19) DEFAULT NULL COMMENT '失效时间为空的时候表示永不失效,失效时间为2018-10-10 10:10:10,则表示在该时间之前该账户可用。',
  `lock_state` char(1) DEFAULT NULL COMMENT '锁定状态为空时表示启用,为0时表示锁定,为1时表示启用。',
  `deptno` char(4) DEFAULT NULL,
  `allow_ips` varchar(255) DEFAULT NULL COMMENT '允许访问的IP为空时表示IP地址永不受限,允许访问的IP可以是一个,也可以是多个,当多个IP地址的时候,采用半角逗号分隔。允许IP是192.168.100.2,表示该用户只能在IP地址为192.168.100.2的机器上使用。',
  `createTime` char(19) DEFAULT NULL,
  `create_by` varchar(255) DEFAULT NULL,
  `edit_time` char(19) DEFAULT NULL,
  `edit_by` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of tbl_user
-- ----------------------------
INSERT INTO `tbl_user` VALUES ('06f5fc056eac41558a964f96daa7f27c', 'ls', '李四', 'yf123', 'ls@163.com', '2018-11-27 21:50:05', '1', 'A001', '192.168.1.1,0:0:0:0:0:0:0:1', '2018-11-22 12:11:40', '李四', null, null);
INSERT INTO `tbl_user` VALUES ('40f6cdea0bd34aceb77492a1656d9fb3', 'zs', '张三', 'yf123', 'zs@qq.com', '2018-11-30 23:50:55', '1', 'A001', '192.168.1.1,192.168.1.2,127.0.0.1,0:0:0:0:0:0:0:1', '2018-11-22 11:37:34', '张三', null, null);

/*==============================================================*/
/* DBMS name:      MySQL 5.0                                    */
/* Created on:     2020/3/5 9:12:54                             */
/*==============================================================*/


drop table if exists tbl_dic_type;

drop table if exists tbl_dic_value;

/*==============================================================*/
/* Table: tbl_dic_type                                          */
/*==============================================================*/
create table tbl_dic_type
(
   code                 varchar(255) not null comment '编码是主键,不能为空,不能含有中文。',
   name                 varchar(255),
   description          varchar(255),
   primary key (code)
);

/*==============================================================*/
/* Table: tbl_dic_value                                         */
/*==============================================================*/
create table tbl_dic_value
(
   id                   char(32) not null comment '主键,采用UUID',
   value                varchar(255) comment '不能为空,并且要求同一个字典类型下字典值不能重复,具有唯一性。',
   text                 varchar(255) comment '可以为空',
   order_no             varchar(255) comment '可以为空,但不为空的时候,要求必须是正整数',
   type_code            varchar(255) comment '外键',
   primary key (id)
);
drop table if exists tbl_activity;

drop table if exists tbl_activity_remark;

/*==============================================================*/
/* Table: tbl_activity                                          */
/*==============================================================*/
create table tbl_activity
(
   id                   char(32) not null,
   owner                char(32),
   name                 varchar(255),
   start_date            char(10),
   end_date              char(10),
   cost                 varchar(255),
   description          varchar(255),
   create_time           char(19),
   create_by             varchar(255),
   edit_time             char(19),
   edit_by               varchar(255),
   primary key (id)
);

/*==============================================================*/
/* Table: tbl_activity_remark                                   */
/*==============================================================*/
create table tbl_activity_remark
(
   id                   char(32) not null,
   note_content          varchar(255),
   create_time           char(19),
   create_by             varchar(255),
   edit_time             char(19),
   edit_by               varchar(255),
   edit_flag             char(1) comment '0表示未修改,1表示已修改',
   activity_id           char(32),
   primary key (id)
);
drop table if exists tbl_clue;

drop table if exists tbl_clue_activity_relation;

drop table if exists tbl_clue_remark;

drop table if exists tbl_contacts;

drop table if exists tbl_contacts_activity_relation;

drop table if exists tbl_contacts_remark;

drop table if exists tbl_customer;

drop table if exists tbl_customer_remark;

drop table if exists tbl_tran;

drop table if exists tbl_tran_history;

drop table if exists tbl_tran_remark;

/*==============================================================*/
/* Table: tbl_clue                                              */
/*==============================================================*/
create table tbl_clue
(
   id                   char(32) not null,
   fullname             varchar(255),
   appellation          varchar(255),
   owner                char(32),
   company              varchar(255),
   job                  varchar(255),
   email                varchar(255),
   phone                varchar(255),
   website              varchar(255),
   mphone               varchar(255),
   state                varchar(255),
   source               varchar(255),
   create_by             varchar(255),
   create_time           char(19),
   edit_by               varchar(255),
   edit_time             char(19),
   description          varchar(255),
   contact_summary       varchar(255),
   next_contact_time      char(10),
   address              varchar(255),
   primary key (id)
);

/*==============================================================*/
/* Table: tbl_clue_activity_relation                            */
/*==============================================================*/
create table tbl_clue_activity_relation
(
   id                   char(32) not null,
   clue_id               char(32),
   activity_id           char(32),
   primary key (id)
);

/*==============================================================*/
/* Table: tbl_clue_remark                                       */
/*==============================================================*/
create table tbl_clue_remark
(
   id                   char(32) not null,
   note_content          varchar(255),
   create_by             varchar(255),
   create_time           char(19),
   edit_by               varchar(255),
   edit_time             char(19),
   edit_flag             char(1),
   clue_id               char(32),
   primary key (id)
);

/*==============================================================*/
/* Table: tbl_contacts                                          */
/*==============================================================*/
create table tbl_contacts
(
   id                   char(32) not null,
   owner                char(32),
   source               varchar(255),
   customer_id           char(32),
   fullname             varchar(255),
   appellation          varchar(255),
   email                varchar(255),
   mphone               varchar(255),
   job                  varchar(255),
   create_by             varchar(255),
   create_time           char(19),
   edit_by               varchar(255),
   edit_time             char(19),
   description          varchar(255),
   contact_summary       varchar(255),
   next_contact_time      char(10),
   address              varchar(255),
   primary key (id)
);

/*==============================================================*/
/* Table: tbl_contacts_activity_relation                        */
/*==============================================================*/
create table tbl_contacts_activity_relation
(
   id                   char(32) not null,
   contacts_id           char(32),
   activity_id           char(32),
   primary key (id)
);

/*==============================================================*/
/* Table: tbl_contacts_remark                                   */
/*==============================================================*/
create table tbl_contacts_remark
(
   id                   char(32) not null,
   note_content          varchar(255),
   create_by             varchar(255),
   create_time           char(19),
   edit_by               varchar(255),
   edit_time             char(19),
   edit_flag             char(1),
   contacts_id           char(32),
   primary key (id)
);

/*==============================================================*/
/* Table: tbl_customer                                          */
/*==============================================================*/
create table tbl_customer
(
   id                   char(32) not null,
   owner                char(32),
   name                 varchar(255),
   website              varchar(255),
   phone                varchar(255),
   create_by             varchar(255),
   create_time           char(19),
   edit_by               varchar(255),
   edit_time             char(19),
   contact_summary       varchar(255),
   next_contact_time      char(10),
   description          varchar(255),
   address              varchar(255),
   primary key (id)
);

/*==============================================================*/
/* Table: tbl_customer_remark                                   */
/*==============================================================*/
create table tbl_customer_remark
(
   id                   char(32) not null,
   note_content          varchar(255),
   create_by             varchar(255),
   create_time           char(19),
   edit_by               varchar(255),
   edit_time             char(19),
   edit_flag             char(1),
   customer_id           char(32),
   primary key (id)
);

/*==============================================================*/
/* Table: tbl_tran                                              */
/*==============================================================*/
create table tbl_tran
(
   id                   char(32) not null,
   owner                char(32),
   money                varchar(255),
   name                 varchar(255),
   expected_date         char(10),
   customer_id           char(32),
   stage                varchar(255),
   type                 varchar(255),
   source               varchar(255),
   activity_id           char(32),
   contacts_id           char(32),
   create_by             varchar(255),
   create_time           char(19),
   edit_by               varchar(255),
   edit_time             char(19),
   description          varchar(255),
   contact_summary       varchar(255),
   next_contact_time      char(10),
   primary key (id)
);

/*==============================================================*/
/* Table: tbl_tran_history                                      */
/*==============================================================*/
create table tbl_tran_history
(
   id                   char(32) not null,
   stage                varchar(255),
   money                varchar(255),
   expected_date         char(10),
   create_time           char(19),
   create_by             varchar(255),
   tran_id               char(32),
   primary key (id)
);

/*==============================================================*/
/* Table: tbl_tran_remark                                       */
/*==============================================================*/
create table tbl_tran_remark
(
   id                   char(32) not null,
   note_content          varchar(255),
   create_by             varchar(255),
   create_time           char(19),
   edit_by               varchar(255),
   edit_time             char(19),
   edit_flag             char(1),
   tran_id               char(32),
   primary key (id)
);

搭建开发环境

设置下JDK

创建moudle

 

maven项目结构

        src

                main

                        java

                        resource

                test

                        java

                        resource

        pom.xml

idea的web模块比较老,补全模块  且快速给目录设置...设置性质吧......

也可以邮件包给每个包设置

添加依赖

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.hrui</groupId>
  <artifactId>crm</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>crm Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!-- MySQL数据库连接驱动 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.11</version>
    </dependency>

    <!-- JDBC数据源连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.5</version>
    </dependency>

    <!-- MyBatis框架依赖 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.1</version>
    </dependency>

    <!-- Spring框架依赖的JAR配置 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-oxm</artifactId>
      <version>4.3.9.RELEASE</version>
    </dependency>

    <!-- Spring AOP支持-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.9</version>
    </dependency>

    <!-- MyBatis与Spring整合依赖 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.0</version>
    </dependency>

    <!-- servlet及jstl标签库依赖的JAR配置 -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet.jsp.jstl</groupId>
      <artifactId>jstl-api</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.taglibs</groupId>
      <artifactId>taglibs-standard-spec</artifactId>
      <version>1.2.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.taglibs</groupId>
      <artifactId>taglibs-standard-impl</artifactId>
      <version>1.2.1</version>
    </dependency>

    <!-- 加载jackson插件依赖 -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.7.3</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.7.3</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.7.3</version>
    </dependency>

    <!--poi依赖 用于Excel操作下载办公文档  word等等 java本身只能操作文本文件,操作办公文件需要该依赖-->
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi</artifactId>
      <version>3.15</version>
    </dependency>

    <!-- 文件上传 -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.1</version>
    </dependency>

    <!-- Log4j2依赖的JAR配置 -->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>2.3</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.3</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-jcl</artifactId>
      <version>2.3</version>
    </dependency>

  </dependencies>

  <build>
    <finalName>crm</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>

    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
      </resource>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.*</include>
        </includes>
      </resource>
    </resources>
  </build>
</project>

最好创建完项目就设置下编码格式 这样编码统一

添加配置文件

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--打印日志-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <!--指定实体类包别名-->
    <typeAliases>
        <package name="com.hrui.crm.pojo"/>
    </typeAliases>
    <!--指定mapper包路径-->
    <mappers>
        <package name="com.hrui.crm.mapper"/>
    </mappers>
</configuration>

applicationContext-datasources.xml  数据库连接账密ip改下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="username" value="root"/>
        <property name="password" value="123456!"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/crm?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    </bean>
    <!-- 配置SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 必须注入属性dataSource -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 如果mybatis没有特殊的配置(比如别名等),configLocation可以省去 ;否则,不能省略-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>
    <!-- mapper注解扫描器配置,扫描@MapperScan注解,自动生成代码对象 -->
    <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.hrui.crm.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 配置事务 -->
    <aop:config>
        <aop:pointcut expression="execution(* com.hrui.crm..service.*.*(..))" id="allMethodPointcut"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="allMethodPointcut"/>
    </aop:config>
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="edit*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="do*" propagation="REQUIRED" rollback-for="Exception"/>
            <tx:method name="*" propagation="REQUIRED" read-only="true"/>
        </tx:attributes>
    </tx:advice>
</beans>

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 加载系统配置文件
    <context:property-placeholder location="classpath:*.properties" />-->
    <!-- 扫描注解 -->
    <context:component-scan base-package="com.hrui.crm.service" />
    <!-- 导入数据相关配置 -->
    <import resource="applicationContext-datasource.xml" />
</beans>

applicationContext-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
    <!-- dispatcherServlet截获所有URL请求 -->
    <mvc:default-servlet-handler />
    <!-- spring mvc 扫描包下的controller -->
    <context:component-scan base-package="com.hrui.crm.controller"/>
<!--    <context:component-scan base-package="com.hrui.crm.settings.web.controller"/>-->
<!--    <context:component-scan base-package="com.hrui.crm.workbench.web.controller"/>-->
    <!-- 配置注解驱动 -->
    <mvc:annotation-driven/>
    <!-- 配置视图解析器 -->
    <bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

<!--    <mvc:interceptors>-->
<!--        <mvc:interceptor>-->
<!--            &lt;!&ndash;配置拦截的请求&ndash;&gt;-->
<!--            <mvc:mapping path="/settings/**"/>-->
<!--            <mvc:mapping path="/workbench/**"/>-->
<!--            &lt;!&ndash;配置排除拦截的请求(优先级高)&ndash;&gt;-->
<!--            <mvc:exclude-mapping path="/settings/qx/user/toLogin.do"/>-->
<!--            <mvc:exclude-mapping path="/settings/qx/user/login.do"/>-->
<!--            &lt;!&ndash;拦截器类&ndash;&gt;-->
<!--            <bean class="com.bjpowernode.crm.settings.web.interceptor.LoginInterceptor"/>-->
<!--        </mvc:interceptor>-->
<!--    </mvc:interceptors>-->

<!--    &lt;!&ndash; 配置文件上传解析器 id:必须是multipartResolver&ndash;&gt;-->
<!--    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">-->
<!--        <property name="maxUploadSize" value="#{1024*1024*5}"/>-->
<!--        <property name="defaultEncoding" value="utf-8"/>-->
<!--    </bean>-->
</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         id="dataservice" version="3.0">
  <display-name>dataservice application</display-name>
  <!-- spring监听器加载applicationContext.xml配置文件 -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <!--这里制定了applicationContext.xml,applicationContext.xml指定了业务层包扫描和持久层Mybatis配置-->
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <!-- spring字符过滤器 -->
  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <!-- Spring mvc分发servlet -->
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <!--指定了applicationContext-mvc.xml,指定了控制器-->
      <param-value>classpath:applicationContext-mvc.xml</param-value>
    </init-param>
    <!--服务器启动即创建DispatcherServlet实例-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
  <!-- 欢迎页,默认进入index controller -->
  <welcome-file-list>
    <welcome-file>/</welcome-file>
  </welcome-file-list>
</web-app>

这几个配置文件的调用关系

applicationContext-datasource.xml里用了mybatis-config.xml

applicationContext.xml里用了applicationContext-datasource.xml

 

web.xml里用了applicationContext.xml和applicationContext-mvc.xml

 项目运行在tomcat的webapps下

        webapps

                |->crm

                        |->.html,

                        |->.css,

                        |->.js,

                        |->.img

                        |->WEB-INF(该目录里的内容是相对安全的)

                                |->web.xml

                                |->classes(编译后的java的.class文件)

                                |->lib(所有依赖)

配置tomcat

 

UML建模工具:Rational Rose    Visio   staruml

个人理解:可视化低代码平台前生  工作流

SpringMVC默认支持参数类型:控制器方法里的参数

1)HttpServletRequest对象

2)HttpServletResponse对象

3)HttpSession对象

4)Model/ModelMap对象

5)Map<String,Object>对象

注意:Map,Model,ModelMap和request一样,都使用请求作用域进行数据传递,所以服务器端的跳转在传值时候必须是请求转发.   重定向想携带数据只有HttpSession
 

现在项目大概结构是这样的

也就是说启动tomcat后默认访问localhost:8080/crm/

现在动态jsp都放在了WEB-INF目录下,想访问的话要通过控制器去跳转

现在演示下控制器跳转

 因为在applicationContext-mvc.xml里配置了视图解析器,会自动进行前后缀拼接

 跳转到index.jsp页面后

 

 

 

现在开始做登录需求

1.用户名和密码不能为空

2.用户名或者密码错误,用户已过期,用户状态被锁定,ip受限,都不能登录成功

3.登录成功之后,所有业务页面现实当前用户的名称

4.实现10天记住密码

5.登录成功之后,跳转到业务主页面

6.登录失败,页面不跳转,提示信息

看下数据库里的tbl_user表

 ip受限:

比如说在用户第一次或多次登录的IP在第一次或者进行某个操作(充值后,看具体需求)进行记录

然后为保障账号安全,以后每次登录,判断用户登录IP.是否为常用IP,如果不是或者某个条件进行某些限制

登录上传的参数一般是账号,密码,是否记住我什么的

先用个实体类对参数  大概思路是这样

关于Mybatis代码生成器

新建个模块用来生成代码用

 

 添加mybatis逆向工程插件  插件可以独立运行

添加mabatis逆向工程配置文件

配置告诉mybatis数据库连接信息

生成的代码保存目录

表的信息

在resource下新建一个generator.properties,用来配置数据库相关信息 根据自己的来

代码里少写个jdbc    com.mysql.cj.jdbc.Driver

jdbc.driverLocation=D:/testDir/Maven/repository_g/mysql/mysql-connector-java/5.1.43/mysql-connector-java-5.1.43.jar
jdbc.driverClass=com.mysql.cj..Driver
jdbc.connectionURL=jdbc:mysql://127.0.0.1:3306/mycrm_db
jdbc.userId=root
jdbc.password=yf123

generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>

    <!--指定mysql数据库驱动-->
    <!--<classPathEntry location="E://repository-p2p//mysql//mysql-connector-java//5.1.43//mysql-connector-java-5.1.43.jar"/>-->

    <!--导入属性配置-->
    <properties resource="generator.properties"></properties>

    <!--指定特定数据库的jdbc驱动jar包的位置-->
    <classPathEntry location="${jdbc.driverLocation}"/>

    <context id="default" targetRuntime="MyBatis3">

        <!-- optional,旨在创建class时,对注释进行控制,false生成注释,true无注释 -->
        <commentGenerator>
            <property name="suppressDate" value="false"/>
            <property name="suppressAllComments" value="false"/>
        </commentGenerator>

        <!--jdbc的数据库连接 -->
        <jdbcConnection
                driverClass="${jdbc.driverClass}"
                connectionURL="${jdbc.connectionURL}"
                userId="${jdbc.userId}"
                password="${jdbc.password}">
        </jdbcConnection>


        <!-- 非必需,类型处理器,在数据库类型和java类型之间的转换控制-->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>


        <!-- Model模型生成器,用来生成含有主键key的类,记录类 以及查询Example类
            targetPackage     指定生成的model生成所在的包名
            targetProject     指定在该项目下所在的路径|指定生成到的工程名称
        -->
        <javaModelGenerator targetPackage="com.bjpowernode.crm.workbench.transaction.model"
                            targetProject="D:/testDir/javaee/projects/crm/crm001/src/main/java">

            <!-- 是否允许子包,即targetPackage.schemaName.tableName -->
            <property name="enableSubPackages" value="false"/>
            <!-- 是否对model添加 构造函数 true添加,false不添加-->
            <property name="constructorBased" value="false"/>
            <!-- 是否对类CHAR类型的列的数据进行trim操作 -->
            <property name="trimStrings" value="true"/>
            <!-- 建立的Model对象是否 不可改变  即生成的Model对象不会有 setter方法,只有构造方法 -->
            <property name="immutable" value="false"/>
        </javaModelGenerator>

        <!--Mapper映射文件生成所在的目录 为每一个数据库的表生成对应的SqlMap文件 -->
        <sqlMapGenerator targetPackage="com.bjpowernode.crm.workbench.transaction.mapper"
                         targetProject="D:/testDir/javaee/projects/crm/crm001/src/main/java">
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>

        <!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码
                type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象
                type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象
                type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口
        -->
        <javaClientGenerator targetPackage="com.bjpowernode.crm.workbench.transaction.mapper"
                             targetProject="D:/testDir/javaee/projects/crm/crm001/src/main/java" type="XMLMAPPER">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>

        <!--
        <table tableName="tbl_user" domainObjectName="User"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>


        <table tableName="tbl_clue" domainObjectName="Clue"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>

        <table tableName="tbl_clue_activity_relation" domainObjectName="ClueActivityRelation"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>

        <table tableName="tbl_clue_remark" domainObjectName="ClueRemark"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>
        -->
        <!--
        <table tableName="tbl_contacts" domainObjectName="Contacts"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>
        <table tableName="tbl_contacts_activity_relation" domainObjectName="ContactsActivityRelation"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>
        <table tableName="tbl_contacts_remark" domainObjectName="ContactsRemark"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>
        -->
        <!--
        <table tableName="tbl_customer" domainObjectName="Customer"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>

        <table tableName="tbl_customer_remark" domainObjectName="CustomerRemark"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>
        -->
        <!--
                <table tableName="tbl_dictionary_type" domainObjectName="DictionaryType"
                       enableCountByExample="false" enableUpdateByExample="false"
                       enableDeleteByExample="false" enableSelectByExample="false"
                       selectByExampleQueryId="false">
                </table>


                <table tableName="tbl_dictionary_value" domainObjectName="DictionaryValue"
                       enableCountByExample="false" enableUpdateByExample="false"
                       enableDeleteByExample="false" enableSelectByExample="false"
                       selectByExampleQueryId="false">
                </table>


                <table tableName="tbl_marketing_activities" domainObjectName="MarketingActivities"
                       enableCountByExample="false" enableUpdateByExample="false"
                       enableDeleteByExample="false" enableSelectByExample="false"
                       selectByExampleQueryId="false">
                </table>


                <table tableName="tbl_marketing_activities_remark" domainObjectName="MarketingActivitiesRemark"
                       enableCountByExample="false" enableUpdateByExample="false"
                       enableDeleteByExample="false" enableSelectByExample="false"
                       selectByExampleQueryId="false">
                </table>
                 -->

        <table tableName="tbl_transaction" domainObjectName="Transaction"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>
        <table tableName="tbl_transaction_history" domainObjectName="TransactionHistory"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>
        <table tableName="tbl_transaction_remark" domainObjectName="TransactionRemark"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false"
               selectByExampleQueryId="false">
        </table>

    </context>
</generatorConfiguration>

 运行mybatis逆向工程,根据指定表生成java代码,保存到指定目录.

双击或者运行

 

对应实体类和Mapper接口  xml文件就生成好了

因为配置文件里配置@Mapper 所以@Mapper可以省了

 写Service层

 控制器  返回类  

user.getExpireTime()写成user.getEditTime改下

 

其实控制器也可以直接用map接收参数

user.getExpireTime()写成user.getEditTime改下

 用的是提交表单的方式

用submit提交表单

现在改成ajax发送异步请求试下

 控制器

 启动测试

登录成功后,一般会将登录用户信息保存在session中

 

 

 关于回车登录功能:思路  添加按下键盘事件,比如按键是否是回车键  是的话  发送ajax请求

 

实现记住我功能

 这个那个那个这个

 

安全退出,客户端点击退出, 清空Cookie  销毁Session

具体步骤就是用户点击退出  请求到控制器, 控制器里清空Cookie   销毁Session

再转发到登录页面

控制器

 

 

jsp代码

测试·

 

这里登录是存在问题的 比如说

现在是请求控制器转发

http://localhost:8080/crm/workindex.do 别人只要知道这个url 就可以访问你的接口

因此需要对请求接口进行拦截验证

接下来要做的就是传说中的在SpringMVC中配置拦截器(过滤器也是可以的)这里演示拦截器

过滤器是Servlet层面的  实现 Filter   然后在web.xml配置过滤器

java的拦截器指SpringMvc中的:1.实现 HandlerInterceptor

                        --pre 请求到达控制器之前执行

                        --post

                        --after

              2.注册拦截器

需求:只有登录的用户才能访问某些接口

没有登录的跳转登录页面

上面配置换成下面的,不然后面做下去css样式都访问不了

在applicationContext-mvc.xml配置注册拦截器

 该功能完成

页面切割

<frameset>用来切割页面

<frameset cols="20%,60%,20%">按比例竖着将页面切割成三分

<frameset rows="10%,80%,10%">按比例横着将页面切割成三分

如果又想按行也想按列那就标签里套标签 比如在20%里面套

<frame>用来显示页面发请求获得数据  需要嵌套在<frameset>标签里面

例如

<frameset rows="10%,80%,10%">

<frame src="url具体的请求1" name=f1>

<frame src="url具体的请求2" name=f2>

<frame src="url具体的请求3" name=f3>

</frameset>

每一个<frame>标签好比一个独立的浏览器请求窗口

这是早期的页面切割技术

现在相对用的<div>和<iframe>

<div style="height:10%;width:20%">

        <iframe href="url">

</div>

现在要做的就是登录后,右边显示

理下思路,点击登录 

 

 

 看下这个

workareaFrame是个什么

 那思路就是 让它去请求控制器,然后跳转到具体页面

让他访问mainIndex接口

 

 

 结果就显示张图片,也是相当满意

 测试启动

接着做市场活动相关功能

控制模态窗口的现实与隐藏:

方式1:通过按钮<button data-toggle="model" data-target="某div #id">来显示弹窗   自动会加入到点击事件中

 

方式2:通过js函数控制:

用js选择器:      选中某个div.modal("show");//显示选中的模态窗口

                        选中某个div.modal("hide");//关闭选中的模态窗口

方式3:通过标签的data-dismiss="xxx"

关闭时会自动关闭该窗口   在关闭按钮上加上该属性 该方式只能关闭  不能显示

window.open("url","-blank")打开一个新请求的窗口

模态窗口本质就是原来页面中的一个div

实现市场活动模块

 大概逻辑:用户登录   到工作台   点击市场活动  请求控制器跳转到该页面(在请求转发页面之前想好该跳转的页面需要不需要某些数据,将数据也带过去)

持久层

业务层接口

业务层实现

控制器

 

 

 测试

 

 用jstl标签库遍历

 页面上还有个修改

,里面也用到了这个下拉选  ctrl+c    ctrl+v 

 

下面要做的  点击保存   发送ajax请求  将参数传给控制器 

然后保存到数据库  控制器返回一个list

用mybatis逆向工程来创建好持久层接口和xml文件

 持久层插入的方法和xml已经写好了

直接持久层

控制器

 在jsp中 

 

 关于保存按钮

表单中对应的属性都加了id

 activity/index.jsp

<script type="text/javascript">

	$(function(){
		//给创建按钮添加单击事件
		$("#createActivitybtn").click(function (){
			//弹出窗口前,可以做些窗口初始化的事情
			alert(123)
			//弹出创建市场活动的模态窗口
			$("#createActivityModal").modal("show")
		})

		//给保存按钮添加单机事件
		$("#saveActivity").click(function (){
			//1.收集且校验参数,2.发送请求,3.接收响应
			var owner=$("#create-marketActivityName").val();//这个是下拉选的  不需要trim
			var name=$.trim($("#create-marketActivityName").val());//用户输入的  去空格
			var startTime=$("#create-startTime").val();
			var endDate=$("#create-endTime").val();
			var cost=$.trim($("#create-cost").val());
			var describe=$.trim($("#create-describe").val());

			//需求为所有者和名称不能为空
			if(owner==""){
				alert("所有者不能为空,必须填写")
				return
			}
			if(name==""){
				alert("名称不能为空,必须填写")
				return
			}
			if(startTime==""){
				alert("开始日期不能为空,必须填写")
				return
			}
			if(endDate==""){
				alert("结束日期不能为空,必须填写")
				return
			}
			//需求满足开始时间和结束时间都不能为空 且判断开始时间小于结束时间
			if(startTime!=""&&endDate!=""&&endDate<startTime){
				//使用字符串大小代替日期大小
				alert("结束日期不能比开始日期小")
				return
			}
			//需求 成本只能是非负整数
			var regExp=/^(([1-9]\d*)|0)$/;
			if(!regExp.test(cost)){
				alert("成本只能是非负整数")
				return
			}

			$.ajax({
				url:"saveActivity",
				data:{
					owner:owner,
					name:name,
					startDate:startTime,
					endDate:endDate,
					cost:cost,
					description:describe
				},
				type:"post",
				dataType:"json",
				sussess:function (res){
					if(res.code=="1"){
						//关闭模态窗口
						$("#createActivityModal").modal("hide");
						//刷新市场活动列,显示第一页数据,保持每页显示条数不变
					}
					if(res.code=="0"){
						//模态窗口不关闭  提示信息
						$("#createActivityModal").modal("show");//这行代码可以不写,练下手感而已
						alert(res.msg)
					}
				}
			})
		})

	});

</script>

测试下

 

 

 

description没存进去

应该是哪个字段写错了   

存在的问题是  关闭窗口.再次点击时

原先的数据还是存在

那么思路就是点击创建按钮  先要清空原有的数据

  先给form表单加个id 

 ok了

这里日期不能让用户自己输入,格式无法统一,需要使用日历插件

使用插件步骤:

1.引入开发包:.JS,.CSS,拷贝到webapp目录下

2.创建容器:比如<input  type="text"><div>

3.当容器加载完成之后,对容器调用工具函数:插件的函数调用

演示测试 bootstrap-datetimepicker  日历插件

因在webapp/WEB-INF内容无法直接从外接访问,因此将测试的jsp页面放在webapp/jquery目录里  WEB-INF目录外

 引入js

 测试访问localhost:8080/crm/datetimepickertest.jsphttp://localhost:8080/crm/datetimepickertest.jsp

 存在的问题就是用户还可以自己修改 会造成格式不一样

 加个readonly  这样就只能选,无法修改

 

 存在的问题是英语   因此差评

 要改的话,去引入的文件里找  清空下缓存

下面将插件放入项目中

已经引入了

给这两个添加点击显示日历

 

 这样有个麻烦  如果很多  写的也多  可以用类选择器,或者其他可以一次选多个的

这样写一个就好了  当然方式还有很多

比如说 给input添加name="abc"属性    $("input[name='abc']").detetimepicker({.........})

 为了不让客户修改  加上readonly

测试 

 

这里注意一个细节问题,关于@RequestBody

 

我换一下,加了个contentType

 没有问题,数据也插入了

 

 我再换一个  

contentType:"application/json" 

后端参数没有加@RequestBody时

看下数据库

数据没有进来

那么参数前加个@RequestBody 

但是报错了 

下面我将数据转换一下  后端加了@RequestBody

 测试下   这里点保存F12没看到接口不知道怎么回事

 

 数据也OK

 那么得出的结论:

发送ajax请求

格式使⽤的是默认的ContentType类型application/x-www-form-urlencoded,格式为key1=value1&key2=value2提交到后台 ,不需
要加@requestBody。
使⽤@requestBody.当请求content_type为:application/json类型的请求,数据类型为json时, json格式如下:
{“aaa”:“111”,“bbb”:“222”}
需要加@RequestBody  传统的发送ajax请求 如果将contentType:"application/json"时,需要将data:{XXX}进行Json转换    改成data:JSON.stringify({xxxx})

注意:这里默认的ContentType类型application/x-www-form-urlencoded,因此后端不需要加@RequestBody

数据创建完成了,一般数据创建完,就是关闭小窗口,然后在页面展示(查询),而查询一般都是分页查询

一般客户没有特别指出,可以分为3种情况:

1.当市场活动主页面加载完成,查询所有数据的第一页以及所有数据的总条数

2.当用户点击"查询"按钮,查询所有符合条件数据的第一页以及所有符合条件数据的总条数.

3.当用户切换每页显示条数或页面,查询所有指定每页显示条数和页号的数据以及所有符合条件无数据的总条数

突然发现代码写错了   owner 

 那表里数据只能手动改下了

查询市场活动接口

xml   

 

 原先List中查出来的不过是某一个分页的符合条件数据, 不代表所有符合条件数据

 业务层接口

 

业务层实现类

 

控制器

 前端页面

 测试

现在要做的是,查询栏输入要查询的字段   点击查询  获得分页展示  和总条数

其实就是给查询加个点击事件

然后代码重写一遍?代码重复  所以封装以下  用的时候调用

 

 给查询按钮加id

 

 测试

 实现点击每页显示条数   和具体某页面分页查询

思路  那么第一肯定是添加事件,当选择每页显示条数时候,其他数据都一样  改变下pageSize

当点击 具体某一页时候  那么其他不变  改变pageNum  只要思路有了,那么问题就好解决

现在原先pageSize和pageNum都是写死了,现在把这两值当参数写活

引入Bootstrp Pagination插件

前端使用插件三步骤:

1.引入依赖  

2.创建容器

3.js调用

写个测试页面举例

 

 其实关键就是插件函数里的那些参数,只需要将参数给函数就好

主要的几个参数    

每页显示条数和页数是用户选的   总条数是后端给的  显示最大页数是计算出来的 

将该插件在项目里运用

1.引入

2.容器

3.js调用  要考虑的是,在哪里进行分页底部这个DIV的调用

 可以将调用该分页的js代码放在success:function(){xxx}里面 

具体代码

 这样基本就OK了 

当点击查询时候 ,每页显示条数会变成10条 

但是有可能客户需求是  我可以改变每页显示   且查询时候 按设置的每页多少条查询

每次点击查询  都默认显示10条因为代码里写死了

需求是第一次时候是第一页  10条

但是后面允许客户指定每页条数  查询

 那么在点之前 我们去获取下  这个显示条数  然后传值进去  写活就好了

queryAcitivityByConditionForPage(1,$("#demo_pag1").bs_pagination("getOption","rowsPerPage"))

下图要获得的属性名写错了  是"rowsPerPage"

还有在页面点击创建,创建完市场活动后,也应该让每页显示不变  且去调用列表

这样 点击查询保持每页显示条数不变的需求就完成了

发现创建保存市场活动报错了   看了下

 当contenType:'application/json'时候   后端实体类接收参数,就需要加@RequestBody了

下面做删除市场活动功能

首先完成 点击全选按钮  选中页面所有  取消全选  则取消所有

先给全选按钮加个单击事件

 简化写法   自己测试玩下

下面要做的需求是    如果下面的一个checkbox 取消选中了  那么上面的全选按钮要自动取消掉

如果下面的checkbox全选中了   那么上面的全选自动要勾上  

 这里存在一个问题,当页面切换  跳转页面的时候  假如原先是全选的,但是跳转后  全选按钮是选中状态,但是新查回来的数据没有选中.原因是新查回来的数据没有去点击过,没有选中按钮是情有可原,原来如此,理所当然的,那么也就是说,当页面切换时候  如果原来的全选按钮是选中状态 将选中的全选按钮也取消选中状态就可以了

 这个函数记一下

现在写删除市场活动后端Mapper

.XML

 Service接口

 impl

控制器

 现在给删除按钮添加单击事件

先给删除按钮添加id  id名改成 deleteActivitys  写错了

 js代码  因为后端参数接收是String[] ids   所有传过去 ids=xxx&&ids=xxx

这玩意可以研究下

关于Ajax中的contentType,dataType_fhrui的博客-CSDN博客

原先写成0了 

下面做修改功能

思路 选中某条数据 (且只能选一条数据进行修改)点击修改按钮  弹出窗口且窗口各个组件有值  然后进行修改  传递给后端  更具id进行修改

mapper

.XML

 Service

 impl

Controller

 前端页面  修改按钮加个id

 添加单击事件

 

那么首先所有者,需要比较后端传过来的owner和 原先就传到jsp页面的比较,如果相等 那么就是该所有者 如何比较下拉选? 很简单  将后过来的owner直接赋值给select  浏览器会自动赋值比对每一个option

下拉列表的id

 

开始时间和结束时间 可能也需要修改,原先此函数是写过的 用类选择器的原因,如果用id选择器的话就需要一个个去点  类选择器可以给多个控件添加

 

 这样修改的表单基本弄好了

下面做保存按钮,点击保存(修改)更新  数据库该条数据   然后前端得到响应 自动关闭修改市场活动窗口  刷新市场活动列表

关于参数封装的建议:

1.如果参数用于查询条件,或者参数之间不是属于一个实体类对象,建议用Map接收

2.如果是增删改操作(写数据操作),并且参数本身就是一个实体类对象,建议封装成实体类接收

 Mapper接口

 .XML

 Service

 Impl

控制器

前端页面添加更新按钮的单击事件  id改成saveEditActivityById  不然和修改按钮id相同了

 

 

 

 

 

使用Jquery获取或者设置指定元素的value属性值:

获取:选择器.val();

设置:选择器.val(属性值); 

 这样修改  更新就完成了

接着做批量导出

1)给'批量导出'按钮添加单击事件,发送导出请求

2)控制器收到请求,查询所有的是市场活动

3)创建一个ecxel文件并且将查询出来的市场活动都写到Excel文件中

4)将生成的Excel文件输出到浏览器(文件下载)

技术准备:

1)使用java生成Excel文件:

        1.1)关于办公文档使用:需要图形化API(学起来比较难,直接用插件) 可以插件有 iText(收费)   apache-poi插件免费.它将办公文档的所有元素封装成普通的java类.程序员通过操作这些类达到操作办公文档的目的.

也就是说

行---代表行的类    HSSFRow

列---代表列的类    HSSFCell

页---代表页的类    HSSFSheet

文件---代表文件的类  HSSFWorkbook     

样式--- HSSFCellStyle 用来修饰边框颜色字体

上面5个是操作Excel文件最基本的类 当然还有很多

2)文件下载:

先演示apache-poi的使用,生成Excel文件并写入数据

引入依赖 这里用的版本是3.15   新出的3.17将很多类进行了合并

使用封装类来生成Excel文件

package poi;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

/**
 * 使用apache-poi生成Excel文件
 * @author hrui
 * @date 2022/8/7 1:24
 */
public class CreateExcelTest {

    public static void main(String[] args) throws IOException {
        //创建HSSFWorkbook对象,对应一个Excel文件
        HSSFWorkbook wb=new HSSFWorkbook();
        //创建页对象 需要指定是哪个文件的页  那么用wb对象去创建
        //HSSFSheet hs=wb.createSheet();//这样默认是sheet1  sheet2等  可以指定
        HSSFSheet hs=wb.createSheet("学生列表");
        //列是属于行的  所以先创建行   再创建列 需要指定是哪一页的行与列  所以用hs创建
        HSSFRow hr=hs.createRow(0);//行的下标 从0开始
        //行里创建列  下标也是从0开始
        HSSFCell hc= hr.createCell(0);
        //列里有列名的话就加列名
        hc.setCellValue("学号");

        //再创建一列  第二列 因为是同一行  所以用同一个行对象去创建列
        hc=hr.createCell(1);
        hc.setCellValue("姓名");//这里可以放心用同一个列对象  没关系
        hc=hr.createCell(2);
        hc.setCellValue("年龄");


        //现在  三个列名已经好了  假如我需要放10个学生对象写到该Excel里 先使用sheet页对象创建10行
        for(int i=1;i<=10;i++){
            //用页创建行
            hr=hs.createRow(i);
            //行里每一列放对应学生信息
            hc= hr.createCell(0);
            hc.setCellValue("id="+i);
            hc= hr.createCell(1);
            hc.setCellValue("NAME="+i);
            hc= hr.createCell(2);
            hc.setCellValue("age="+i);
        }
        //调用工具函数,生成Excel文件   目录必须提前创建好,文件名会自动生成
       OutputStream os= new FileOutputStream("D:\\serverDir\\students.xls");
        wb.write(os);//传个输出流

        os.close();
        wb.close();

        System.out.println(".....");

    }
}

 

 调样式

 

文件已经在服务器生成了,那么用户需要下载下来

演示文件下载

创建一个filedowntest.jsp    ActivityController    fileDownload()用来演示文件下载

因为做了拦截器  所以先登录下,不然无法访问接口

 

 

下面做批量导出市场活动

用户导出批量导出按钮->开始向后台发送同步请求->后端接口收到请求->查询所有市场活动->创建Excel文件并且将查询结果写入该文件->响应Excel文件

Mapper

.XML

Service

Impl

控制器  该方法需要优化

 @RequestMapping("/exportActivitys")
    public void exportActivitys(HttpServletResponse response) throws IOException {
        List<Activity> list=activityServcice.queryAllActivitys();

        //创建HSSFWorkbook对象,对应一个Excel文件
        HSSFWorkbook wb=new HSSFWorkbook();
        //创建页对象 需要指定是哪个文件的页  那么用wb对象去创建
        //HSSFSheet hs=wb.createSheet();//这样默认是sheet1  sheet2等  可以指定
        HSSFSheet hs=wb.createSheet("市场活动");
        //列是属于行的  所以先创建行   再创建列 需要指定是哪一页的行与列  所以用hs创建
        HSSFRow hr=hs.createRow(0);//行的下标 从0开始
        //行里创建列  下标也是从0开始
        HSSFCell hc=hr.createCell(0);
        hc.setCellValue("ID");
        hc=hr.createCell(1);
        hc.setCellValue("所有责");
        hc=hr.createCell(2);
        hc.setCellValue("活动名称");
        hc=hr.createCell(3);
        hc.setCellValue("开始日期");
        hc=hr.createCell(4);
        hc.setCellValue("结束日期");
        hc=hr.createCell(5);
        hc.setCellValue("成本");
        hc=hr.createCell(6);
        hc.setCellValue("描述");
        hc=hr.createCell(7);
        hc.setCellValue("创建时间");
        hc=hr.createCell(8);
        hc.setCellValue("创建者");
        hc=hr.createCell(9);
        hc.setCellValue("修改时间");
        hc=hr.createCell(10);
        hc.setCellValue("修改者");

        Activity activity=null;
        if(list!=null&&list.size()>0) {
            for (int i = 0; i < list.size(); i++) {
                activity = list.get(i);

                hr=hs.createRow(i + 1);
                hc = hr.createCell(0);
                hc.setCellValue(activity.getId());
                hc = hr.createCell(1);
                hc.setCellValue(activity.getOwner());
                hc = hr.createCell(2);
                hc.setCellValue(activity.getName());
                hc = hr.createCell(3);
                hc.setCellValue(activity.getStartDate());
                hc = hr.createCell(4);
                hc.setCellValue(activity.getEndDate());
                hc = hr.createCell(5);
                hc.setCellValue(activity.getCost());
                hc = hr.createCell(6);
                hc.setCellValue(activity.getDescription());
                hc = hr.createCell(7);
                hc.setCellValue(activity.getCreateTime());
                hc = hr.createCell(8);
                hc.setCellValue(activity.getCreateBy());
                hc = hr.createCell(9);
                hc.setCellValue(activity.getEditTime());
                hc = hr.createCell(10);
                hc.setCellValue(activity.getEditBy());
            }
        }

        //调用工具函数,生成Excel文件   目录必须提前创建好,文件名会自动生成

        OutputStream os= new FileOutputStream("D:\\serverDir\\students.xls");
        wb.write(os);//传个输出流

        os.close();
        wb.close();


        //读取服务器上的文件 且返回
        //1.设置响应类型
        response.setContentType("application/octet-stream;charset=UTF-8");
        //2.获取输出流
        OutputStream out=response.getOutputStream();//这个对象是tomcat创建的  让tomcat管理  不需要自己关闭
        /**
         * 3.浏览器收到相应信息后默认都是直接在显示窗口打开响应信息,即使打不开也会调用电脑上的应用来打开,
         * 只有实在打不开才会激活文件下载窗口。可以设置响应头信息,使浏览器接收到响应信息之后,直接激活文件下载
         * 窗口,即使能打开也不打开
         */
        response.addHeader("Content-Disposition", "attachment;filename=mystudentList.xls");
        //输入流读取文件,然后通过输出到浏览器

        InputStream in=new FileInputStream("D:\\serverDir\\students.xls");

        byte[] buff=new byte[1024*6];
        int len=0;
        while((len=in.read(buff))!=-1){
            out.write(buff, 0, len);
        }
        in.close();
        out.flush();
    }

前端页面  按钮加id

 

 

对批量下载优化

wb.write(os);//传个输出流  这是将内存中的数据写到了磁盘中

byte[] buff=new byte[1024*6];
int len=0;
while((len=in.read(buff))!=-1){
    out.write(buff, 0, len);
}

又是将磁盘的数据读回来  写出到客户端

这一来一回白折腾,而且服务器保留那个文件没有必要

    @RequestMapping("/exportActivitys")
    public void exportActivitys(HttpServletResponse response) throws IOException {
        List<Activity> list=activityServcice.queryAllActivitys();

        //创建HSSFWorkbook对象,对应一个Excel文件
        HSSFWorkbook wb=new HSSFWorkbook();
        //创建页对象 需要指定是哪个文件的页  那么用wb对象去创建
        //HSSFSheet hs=wb.createSheet();//这样默认是sheet1  sheet2等  可以指定
        HSSFSheet hs=wb.createSheet("市场活动");
        //列是属于行的  所以先创建行   再创建列 需要指定是哪一页的行与列  所以用hs创建
        HSSFRow hr=hs.createRow(0);//行的下标 从0开始
        //行里创建列  下标也是从0开始
        HSSFCell hc=hr.createCell(0);
        hc.setCellValue("ID");
        hc=hr.createCell(1);
        hc.setCellValue("所有责");
        hc=hr.createCell(2);
        hc.setCellValue("活动名称");
        hc=hr.createCell(3);
        hc.setCellValue("开始日期");
        hc=hr.createCell(4);
        hc.setCellValue("结束日期");
        hc=hr.createCell(5);
        hc.setCellValue("成本");
        hc=hr.createCell(6);
        hc.setCellValue("描述");
        hc=hr.createCell(7);
        hc.setCellValue("创建时间");
        hc=hr.createCell(8);
        hc.setCellValue("创建者");
        hc=hr.createCell(9);
        hc.setCellValue("修改时间");
        hc=hr.createCell(10);
        hc.setCellValue("修改者");

        Activity activity=null;
        if(list!=null&&list.size()>0) {
            for (int i = 0; i < list.size(); i++) {
                activity = list.get(i);

                hr=hs.createRow(i + 1);
                hc = hr.createCell(0);
                hc.setCellValue(activity.getId());
                hc = hr.createCell(1);
                hc.setCellValue(activity.getOwner());
                hc = hr.createCell(2);
                hc.setCellValue(activity.getName());
                hc = hr.createCell(3);
                hc.setCellValue(activity.getStartDate());
                hc = hr.createCell(4);
                hc.setCellValue(activity.getEndDate());
                hc = hr.createCell(5);
                hc.setCellValue(activity.getCost());
                hc = hr.createCell(6);
                hc.setCellValue(activity.getDescription());
                hc = hr.createCell(7);
                hc.setCellValue(activity.getCreateTime());
                hc = hr.createCell(8);
                hc.setCellValue(activity.getCreateBy());
                hc = hr.createCell(9);
                hc.setCellValue(activity.getEditTime());
                hc = hr.createCell(10);
                hc.setCellValue(activity.getEditBy());
            }
        }

        //调用工具函数,生成Excel文件   目录必须提前创建好,文件名会自动生成

//        OutputStream os= new FileOutputStream("D:\\serverDir\\students.xls");
//        wb.write(os);//传个输出流
//
//        os.close();
//        wb.close();


        //读取服务器上的文件 且返回
        //1.设置响应类型
        response.setContentType("application/octet-stream;charset=UTF-8");
        //2.获取输出流
        OutputStream out=response.getOutputStream();//这个对象是tomcat创建的  让tomcat管理  不需要自己关闭
        /**
         * 3.浏览器收到相应信息后默认都是直接在显示窗口打开响应信息,即使打不开也会调用电脑上的应用来打开,
         * 只有实在打不开才会激活文件下载窗口。可以设置响应头信息,使浏览器接收到响应信息之后,直接激活文件下载
         * 窗口,即使能打开也不打开
         */
        response.addHeader("Content-Disposition", "attachment;filename=mystudentList.xls");
        //输入流读取文件,然后通过输出到浏览器

//        InputStream in=new FileInputStream("D:\\serverDir\\students.xls");
//
//        byte[] buff=new byte[1024*6];
//        int len=0;
//        while((len=in.read(buff))!=-1){
//            out.write(buff, 0, len);
//        }
//        in.close();
        wb.write(out);
        wb.close();
        out.flush();
    }

这样就优化好了

下面做选择性导出功能,就是用户通过左边check选择要导出的数据

 那么大概思路就是 客户端选择需要导出的数据id->控制器接收参数查询->其他和上面一样

 

 

控制器应该可以用一个sql解决  但是一时没整好,改日请教下 

 

选择性导出做好了

记住,下载文件用的一定是同步请求,上传可以同步也可以异步

下面开始做  上传Excel表格  导入市场活动(Excel)

举例演示文件上传  前端页面  fileuploadtest.jsp   后端还是在ActivityController里加方法 接收文件上传请求  fileupload()

在webapp下新建个fileuploadtest.jsp用来测试

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    String basePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/";
%>
<html>
<head>
    <base href="<%=basePath%>">
    <title>演示文件上传</title>
</head>
<body>

<%--
表单组件标签<input type="text|password|radio|checkbox|hidden|button|submit|reset|file">
还有<select>,<textarea>等
get和post请求的区别:
get:参数通过请求头提交到后台,通过url后面参数拼接.简单文本数据(不能提交二进制数据).参数长度有限制.参数相对不安全.效率相对较高
另外get请求 浏览器会缓存静态资源  js css图片等
post:参数通过请求体提交到后台.即能提交文本数据,也能提交二进制数据
文件上传的表单必须满足三个条件:
1.表单组件标签只能用:<input type="file" value="xxx">会自动弹框让用户选择文件
2.请求方式只能用post
3.表单编码格式只能用:mutipart/form-data
    根据HTTP协议规定,浏览器每次向后台提交参数,都会对参数进行统一编码
    以前没有设置过,默认采用编码格式是urlencoded,此种编码格式只能对文本数据进行编码
    文件上传的表单格式必须是mutipart/form-data
--%>
<form action="fileupload" method="post" enctype="multipart/form-data">
    <input type="file" name="myFile">
    <input type="text" name="userName"><%--这样userName也会传过去--%>
    <input type="submit" value="提交">
</form>
</body>
</html>

 因设置了拦截器

先登录  在访问http://localhost:8080/crm/fileuploadtest.jsp

 

 对应的文件会保存在服务器指定路径下

 对文件上传小优化

这里特去查了下若依

下载文件 若依用的是同步请求

这里注意注意注意:记住下载文件必须是同步请求,上传文件可以是同步请求,也可以异步

这里测试时候用的是同步请求,实际导入用的是ajax异步请求

这里上传文件可以是图片,视频,各种文件,大小的话,一般是前端做控制,

后端也可以做控制,具体使用时候,判断后缀是什么文件

文件上传基本已经测试完了,导入Excel首先也是文件上传

然后通过图形化API----->APACHE--->POI进行解析

行---代表行的类    HSSFRow

列---代表列的类    HSSFCell

页---代表页的类    HSSFSheet

文件---代表文件的类  HSSFWorkbook     

样式--- HSSFCellStyle 用来修饰边框颜色字体 这里不演示解析样式

新建个类演示用apache-poi解析Excel文件

package poi;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import java.io.*;

/**
 * @author hrui
 * @date 2022/8/8 6:46
 */
public class ParseExcelTest {
    public static void main(String[] args) throws IOException {

        //根据指定的Excel文件 生成HSSFWorkbook对象,封装了Excel文件的所有信息
        InputStream is=new FileInputStream("D:\\serverDir\\students.xls");
        HSSFWorkbook wb=new HSSFWorkbook(is);

        //根据wb获取HSSFSheet(页)对象 该对象封装了一页的所有信息
        HSSFSheet hs=wb.getSheetAt(0);//页的下标 0开始


        HSSFRow hr=null;
        HSSFCell hc=null;
        //根据页对象获取行 循环获取行
        for(int i=0;i<hs.getLastRowNum();i++) {
            hr = hs.getRow(i);//行的下标

            for(int j=0;j<hr.getLastCellNum()-1;j++){//hr.getLastCellNum()获得的是最后一列下标+1,所以我们要-1
               //获取每行的列
               hc= hr.getCell(j);
               //获取列中的数据 根据具体字段的类型获取
               if(hc.getCellType()==HSSFCell.CELL_TYPE_STRING){
                   System.out.print(hc.getStringCellValue()+" ");
               }else if(hc.getCellType()==HSSFCell.CELL_TYPE_NUMERIC){
                   System.out.print(hc.getNumericCellValue()+" ");
               }else if(hc.getCellType()==HSSFCell.CELL_TYPE_BOOLEAN){
                   System.out.print(hc.getBooleanCellValue()+" ");
               }else if(hc.getCellType()==HSSFCell.CELL_TYPE_FORMULA){
                   System.out.print(hc.getCellFormula()+" ");
               }else{
                   System.out.println(" ");
               }
            }
            System.out.println();
        }
    }
}

内循环中可以封装一下  可以自己整个工具类

package poi;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import java.io.*;

/**
 * @author hrui
 * @date 2022/8/8 6:46
 */
public class ParseExcelTest {
    public static void main(String[] args) throws IOException {

        //根据指定的Excel文件 生成HSSFWorkbook对象,封装了Excel文件的所有信息
        InputStream is=new FileInputStream("D:\\serverDir\\students.xls");
        HSSFWorkbook wb=new HSSFWorkbook(is);

        //根据wb获取HSSFSheet(页)对象 该对象封装了一页的所有信息
        HSSFSheet hs=wb.getSheetAt(0);//页的下标 0开始


        HSSFRow hr=null;
        HSSFCell hc=null;
        //根据页对象获取行 循环获取行
        for(int i=0;i<hs.getLastRowNum();i++) {
            hr = hs.getRow(i);//行的下标

            for(int j=0;j<hr.getLastCellNum()-1;j++){//hr.getLastCellNum()获得的是最后一列下标+1,所以我们要-1
               //获取每行的列
               hc= hr.getCell(j);
               //获取列中的数据 根据具体字段的类型获取
                System.out.print(ParseExcelTest.getCellValueForStr(hc)+" ");
            }
            System.out.println();
        }
    }

    /**
     * 返回每一行中,每一列的值,为简化,全部返回字符串
     * @param hc
     * @return
     */
    public static String getCellValueForStr(HSSFCell hc){
        if(hc.getCellType()==HSSFCell.CELL_TYPE_STRING){
            return hc.getStringCellValue();
        }else if(hc.getCellType()==HSSFCell.CELL_TYPE_NUMERIC){
            return hc.getNumericCellValue()+" ";
        }else if(hc.getCellType()==HSSFCell.CELL_TYPE_BOOLEAN){
            return hc.getBooleanCellValue()+" ";
        }else if(hc.getCellType()==HSSFCell.CELL_TYPE_FORMULA){
            return hc.getCellFormula()+" ";
        }else{
            return "";
        }
    }
}

上传文件和解析Excel测试完了,下面可以导入Excel文件

用:<input type="file" value="xxx">会自动弹框让用户选择文件

那么思路就是每遍历一行  在内循环里将每一列的值封装到对象中,然后存到数据库

具体流程大概是:用户点击导入按钮,弹出市场活动的模态窗口,用户选择导入的文件,用户点击导入按钮,发送ajax请求,后端获取文件,(把文件写入磁盘,解析磁盘文件)这两步后面肯定合起来,解析文件数据封装起来,存入数据库,响应信息且返回导入的条数

上面测试时候用的submit同步请求,下面用ajax异步请求演示导入

Mapper

.XML

Service

Impl

Controller

关于文件导入问题,一般会和客户做个约定,Activity  id字段一般是后端生成或主键自增策略自动生成

而这里将所有者暂定义为导入该文件的用户,当然实际开发时,与客户协商具体已实际可行的需求为准

控制器代码

    //导入市场活动
    @RequestMapping("/importActivitysFile")
    @ResponseBody
    public Object importActivitys(MultipartFile activityFile,HttpSession session) {
        User user=(User)session.getAttribute("sessionUser");
        try {
            String originalFilename=activityFile.getOriginalFilename();

            //路径必须创建好,文件会自动创建
            File file=new File("D:\\serverDir\\"+originalFilename);
            //把文件在服务指定的目录中生成一个同样的文件
            activityFile.transferTo(file);

            //用APACHE-POI解析Excel文件
            //根据指定的Excel文件 生成HSSFWorkbook对象,封装了Excel文件的所有信息
            InputStream is=new FileInputStream("D:\\serverDir\\"+originalFilename);
            HSSFWorkbook wb=new HSSFWorkbook(is);

            //根据wb获取HSSFSheet(页)对象 该对象封装了一页的所有信息
            HSSFSheet hs=wb.getSheetAt(0);//页的下标 0开始


            HSSFRow hr=null;
            HSSFCell hc=null;
            Activity activity=null;
            //根据页对象获取行 循环获取行

            List<Activity> list=new ArrayList<>();
            for(int i=1;i<hs.getLastRowNum();i++) {//拿数据从1开始
                hr = hs.getRow(i);//行的下标
                activity=new Activity();
                //一般该文件都会和客户约定好,这里比如说已经约定了 导入者作为所有者,当然具体的已实际可行需求为准
                activity.setId(UUID.randomUUID().toString().replace("-", ""));
                activity.setOwner(user.getId());
                //创建者和创建时间
                activity.setCreateTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                activity.setCreateTime(user.getName());
                for(int j=0;j<hr.getLastCellNum()-1;j++){//hr.getLastCellNum()获得的是最后一列下标+1,所以我们要-1
                    //获取每行的列
                    hc= hr.getCell(j);
                    //获取列中的数据 根据具体字段的类型获取
                    String cValue=ActivityController.getCellValueForStr(hc);
                    //格式肯定需要提前约定好,第一列是什么 第二列是什么
                    if(j==0){
                        activity.setName(cValue);
                    }else if(j==1){
                        activity.setStartDate(cValue);
                    }else if(j==2){
                        activity.setEndDate(cValue);
                    }else if(j==3){
                        activity.setCost(cValue);
                    }else if(j==4){
                        activity.setDescription(cValue);
                    }
                }
                list.add(activity);
            }
            int num=activityServcice.saveActivitysByList(list);
            return new ReturnObject().setCode("1").setData(num);

        } catch (IOException e) {
            e.printStackTrace();
            return new ReturnObject().setCode("0").setMsg("系统繁忙,稍后重试");
        }
    }

    /**
     * 返回每一行中,每一列的值,为简化,全部返回字符串
     * @param hc
     * @return
     */
    public static String getCellValueForStr(HSSFCell hc){
        if(hc.getCellType()==HSSFCell.CELL_TYPE_STRING){
            return hc.getStringCellValue();
        }else if(hc.getCellType()==HSSFCell.CELL_TYPE_NUMERIC){
            return hc.getNumericCellValue()+" ";
        }else if(hc.getCellType()==HSSFCell.CELL_TYPE_BOOLEAN){
            return hc.getBooleanCellValue()+" ";
        }else if(hc.getCellType()==HSSFCell.CELL_TYPE_FORMULA){
            return hc.getCellFormula()+" ";
        }else{
            return "";
        }
    }

控制器后期优化

前台代码

用户点击批量导入

然后弹窗

选择文件   点击导入按钮  上传成功则关闭页面,失败则不关闭,显示失败原因

那首先就是点击 先加上id

点击导入市场活动数据  便会弹出窗口

 那我们直接给导入按钮添加点击事件发送ajax请求即可

用:<input type="file" value="xxx">会自动弹框让用户选择文件

 点击选择好文件后,需要拿到该id  的value属性值

但用

var activityFileName=$("#activityFileName").val();只能获取文件名

 测试下

 点击导入

那我们要拿到的是文件本身

而这里也恰好可以做下文件后缀的验证,看是不是xls文件

//给导入按钮添加单击事件
		$("#importActivityBtn").click(function (){
			//alert(123)
			//收集页面参数
			//但是获取文件的话,这样获取只能获取文件名
			var activityFileName=$("#activityFile").val();
			//alert(activityFileName)
			//截取字符串   str.substr(startIndex,length);   str.substring(startIndex,endIndex)
			//但是这里用这两个函数都不是很合适,虽然可以用  另外一个重载函数  str.substr(startIndex)从下标为startIndex的字符开始截取,截取到最后
			var suffix=activityFileName.substr(activityFileName.lastIndexOf(".")+1).toLocaleLowerCase();//全部转小写
			//注意文件后缀不区分大小写比如 xLs  XLS  Xls等都是合法的
			if(suffix!="xls"){
				alert("只支持xls文件")
				return
			}
			//获取文件 <input type="file"> value是文件名  文件在该组件的dom对象的属性中
			//那么如何获取组件的dom对象
			//document.getElementById("activityFile")获取dom对象
			//也可以用下面的$("#activityFile")[0]获取dom对象
			var activityFile=$("#activityFile")[0].files[0]
			//校验文件大小
			if(activityFile.size>5*1024*1024){
				alert("文件大小不超过5M")
				return
			}
			//FormData是ajax提供的接口,可以模拟键值对向后台提交参数
			//FormData最大的优势是不但能提交文本数据,还能提交二进制数据
			var formData=new FormData();
			//参数名与后端接收形参名保持一致
			formData.append("activityFile",activityFile)
			formData.append("userName","张三")
			//文件上传的表单必须满足三个条件:
			//1.表单组件标签只能用:<input type="file" value="xxx">会自动弹框让用户选择文件
			//2.请求方式只能用post
			//3.表单编码格式只能用:multipart/form-data
			//发送ajax请求
			$.ajax({
				url:"importActivitysFile",
				processData:false,//设置ajax向后台提交参数之前,是否把参数统一转换成字符串:true--是,false---不是,否认是true
				contentType:false,//设置ajax向后台提交参数之前,是否把所有的参数统一按urlencoded编码:true---是,false--不是
				data:formData,
				type:"post",
				dataType:"json",
				success:function (res){
					if(res.code==1){
						//提示成功导入记录条数
						alert("成功导入"+res.data+"条记录")
						//关闭模态窗口
						$("#importActivityModal").modal("hide")
						//刷新市场活动列,显示第一页数据,保持每页显示条数不变
						queryAcitivityByConditionForPage(1,$("#demo_pag1").bs_pagination("getOption","rowsPerPage"))
					}else{
						//提示信息
						alert(res.msg)
						//模态窗口不会自动关闭,这里演示逻辑
						$("#importActivityModal").modal("show")
					}
				}
			})

		});

 这样功能实现了  但是控制器代码肯定是需要优化

优化后

接下来要做的  

 要查的不光是市场活动信息,还有与市场活动对应的意见信息

 tbl_activity  和  tbl_activity_remark    一对多关系

这里我们业务的sql查询就是

select * from tbl_activity_remark where  activity_id=xxx

如果需要关联

select a.*,b.* from tbl_activity a inner join tbl_activity_remark on a.id=b.activity_id where b.activity_id=xxxx

那么需要对应的实体类接收

这里我们只需要select * from tbl_activity_remark where  activity_id=xxx去查具体某activity_id对应的所有备注就行了

大概思路是:用户点击超级链接,到一个Controller,控制器查出对应数据,放到作用域中,内部跳转到明细jsp,然后响应HTML

 

业务层

 impl

控制器 

另外一个备注表的备注信息也需要查出来

用mybatis逆向工程先生成代码

也就表名和实体类需要修改下

 运行生成文件

 

 持久层

 

 业务层

控制器

引入jstl标签库

 从作用域中取数据

 

 备注部分

 这里注意使用标签保存数据:

        给标签加属性:

                如果是表单组件标签,优先使用value属性,只有value不方便使用时,使用自定义属性,

                如果不是表单组件标签,不推荐使用value,推荐使用自定义属性.

        获取属性时:

                如果获取表单组件标签的value属性值:两种方式

                                                                1.dom对象。value

                                                                2.jquery对象.val()

                如果自定义的属性,不管是什么标签,只能用:jquery对象.attr("属性名") 

 原先备注业务层写错

 那么点击

跳转

接着做添加备注功能

 这里做保存的时候,就插入,然后在前端直接添加,不再去查数据库,为了缓解后台压力,当然这么做有好处也有坏处,比如说别人也在查看备注,也在插入数据,那么当前用户时无法实时看到的

插入备注数据成功就前端动态拼接,不成功就不拼接

mapper

XML

 Service

IMPL

 

 关于把页面片段html代码动态显示在页面中:

选择器(父标签).append(htmlStr); 追加显示在指定标签的内部的后边

选择器.html(htmlStr);覆盖显示在指定标签的内部 

选择器(最后个标签).after(htmlStr);追加显示在指定标签的外部的后边,和append有区别

选择器(目标选择器的下面的标签).before(htmlStr);追加显示在指定标签的外部的前面

下面这种只能显示文本信息

选择器.text(htmlStr);覆盖显示在指定标签的内部  

 

 做删除备注:大概思路   前端发起请求,删除备注,成功则动态删除某个div

Mapper

 XML

 Service

Impl

Controller

 

 前端  给所有删除图标添加单击事件

给元素扩展属性:html页面是可扩展的标记语言,可以给指定的标签任意扩展属性,只要属性名符合标识符的命名规则即可随意扩展

我们扩展时一般两个目的:

1.为了让标签保存数据:如果是表单组件标签,优先使用value属性,只有value不方便使用时,使用自定义属性,如果不是表单组件标签,不推荐使用value,推荐使用自定义属性

2.定位标签:优先考虑id属性,其次考虑name属性,只有id和name属性都不方便时,才考虑自定义属性

 那么动态添加时也要加进去

 

 有些地方写错了

 

 

 

市场活动备注删除做完了

其实删除或修改都需要验证下这条备注是不是他写的,这里没有做验证,没有就没有吧

大概思路:点击修改按钮,弹出表单(UUID放在隐藏域里),原来的备注信息显示在上面,进行修改,点击更新保存,前端发起请求,修改该数据,然后得到响应  动态刷新即可 失败的话,模块窗口不关闭,提示信息

用户点击市场活动备注后边的修改图标,获取备注的id和noteContent,将id和noteContent写道修改备注的模态窗口中,弹出修改备注的模态窗口,用户填写表单,用户点击更新按钮,发送请求

需要给所有的动态标签添加单击事件不能用.click   要用on的方式

noteContent放在<h5>标签里 

放进去的时候可以加个id方便后面拿来内容   不过这里还是用父子选择器来拿

 

 

 

 接着改内容,然后更新发送请求

先写后端

Mapper

.XML

Service

Impl

Controller

下面就是给更新按钮添加单击事件

 

 测试

点击线索  请求控制器  控制器跳转到某个具体页面

大概思路用户点击线索,跳转到线索页面,接着页面发起请求,查询各种数据字典数据(下拉选等)

 

 

 页面上将所有下拉选数据放上去渲染

那么现在页面上数据也有了

 点击创建按钮,弹出模态窗口

 下面做创建线索,保存数据

用Mybatis代码生成工具生成.XML和Mapper,其实insert方法已经有了,自己写

 

 

 

 下面做前端

点击保存按钮发送请求  找到创建的模态窗口的保存按钮添加id

data-dismiss="modal"作用是点击关闭模态窗口,我们这里如果失败就不关闭窗口,所以可以去掉

 

 Alt+shift+insert 列编辑模式   偷懒做法

 

给创建按钮添加点击事件,方便下次创建清空属性,做重置

原先用的是

 给创建表单加id,方便重置表单

 

线索页面创建可以了

下拉选数据如果不想有

可以这么做 

注意很多重复功能不做了

下面做查看线索明细页面,点击线索名字跳转到线索具体信息,及备注信息

还有线索和市场活动的关联关系  线索信息来源与市场活动  多对多关系

 

线索明细里牵扯的表有

tbl_clue  线索表

tbl_clue_remark 线索备注表    

tbl_activity 市场活动表

tbl_clue_activity_relation 线索表与市场活动的中间表

线索表和线索备注表   一对多关系

线索表和市场活动表   多对多关系

比如查询某个线索参加过的市场活动 那么我们可能是知道线索的id了

select a.id a.name from tbl_activity a inner join tbl_clue_activity_relation b

on a.id=b.activity_id where b.clue_id=1001

具体内连接还是外连接 就看表里的关联字段能不能为null

查看市场明细,大概思路,用户点击线索名称超级连接携带参数线索id,到控制器,控制器查询携带线索明细,关联备注信息,关联市场信息跳转到明细页面,

从线索开始

Mapper

.XML

 Service

Impl

接着做查询线索备注信息

Mapper

线索备注表还没操作过,先用mybatis的代码生成工具生成Mapper和xml

 对应XML

Service

Impl

 下面还有一个查询  就是线索对应的市场活动

线索表和市场活动表的关系是多对多关系

需求

解除关联:就是拿市场活动id和对应线索的id去市场活动和线索的中间表里去将那条记录删除

还有个添加关联:就是用市场活动id和对应线索的id去市场活动和线索的中间表里去添加一条记录

其实解除关联还有种方式就是拿中间表的id去直接删除,但是那样还需要将中间表的主键id查出来

Mapper

.XML

Service

IMPL

该查的都查了  写控制器

前端页面

 

 

 

 

 

 

 

线索关联市场活动:大概需求,线索和市场活动是多对多关系,因此可以关联多个市场活动

点击关联市场活动,弹出模态窗口,窗口展示所有与该线索关联的市场活动,且有搜索功能,该搜索功能没有查询按钮,

当名字输入完成,自动向后台进行模糊查询(键盘弹起自动向后台发请求)进行市场活动匹配,也可以选择具体或者多个市场活动进行关联  在中间表中做记录

这里主要做的就是搜索框键盘谈起自动发送查询请求 

注意

每次至少关联一个市场活动

同一个线索只能跟同一个线索关联一次(搜索查询时,排除已经关联过的市场活动)

关联成功之后,关闭模态窗口,刷新已经关联过的市场活动列表

关联失败,提示信息,模块窗口不关闭,已经关联过的市场活动列表不刷新

Mapper

.XML

Service

IMPL

Controller

现在做前端

点击关联市场活动搜索框输入内容发送请求,然后处理响应将list展现

我们不需要他自己弹

 

里面的搜索框

 加上id

 

测试

 

接下来要做的是

 点击关联发送请求,在中间表记录对应的线索与市场活动

选了几个市场活动就在中间表里加几条记录

这里注意,对应线索查市场活动的时候,我们是排除已经关联的市场活动的

 也就是说关联成功了,这里不再发请求,而是一次性将关联的市场活动信息返回

用Mybatis逆向工程代码生成工具先生成市场活动和线索的中间表的Mapper和XML

 

 

Mapper

 .XML

Service

Impl

 再写个Mapper,为了批量保存成功后,返回对应关联的市场活动

XML

Service

Impl 

Controller

 下面做前端

找到模态窗口里的关联按钮

 加上id

 加上单击事件

获取参数 就是你选择的市场活动,且没有选择不让提交

 

动态拼接关联的市场活动

 加id进行追加显示

 res写成data了换下

 测试

 这里有个问题,关联后的模态窗口里还有上次关联数据,那么需要每次点击搜索时

都都需要清空里面搜索框和table里内容

接着做解除线索关联市场活动

点击解除关联关系,弹出确认解除对话款,点击确认  发送请求,参数有clueId和activityId

那么就是删除中间表对应alueId和activityId的那条中间表数据

成功了  前端动态删除那条数据

Mapper

 XML

Service

 Impl

Controller

前端解除关联

 给按钮添加单击事件,提示确认解除吗

确认后传回线索id和市场活动id,在中间表进行删除

这个解除关联的id是动态生成的

因不是固有元素,所以要on的那种方式

 且对应作用域传值过来时,也加上

 原先表名写错了

 

这样解除关联好了

接着做线索转换

业务理解:

线索是给初级销售人员使用的,如果线索联系后发现没有购买产品意向,

则进行删除操作,当然事实上不会真的删除,而是做个逻辑删除

,如果说发现线索有购买意向,则将线索信息转换到客户和联系人表中,

该线索做逻辑删除

因为线索表中,有联系人信息和公司信息

拆分为客户和联系人,有跟高级的销售人员跟进

点击之后展现,客户名字,公司名字,称呼,

新建客户:-----公司表

新建联系:具体联系人表

谁创建的,应该可能成功拿提成........

 用户在线索明细页面,点击转换按钮,跳转到线索转换页面

用户在线索转换页面,如果需要创建交易,则填写交易表单数据,点击转换完成线索转换的功能

在线索转换页面,展示fullName,application,company owner

市场活动源可搜索的

数据转换:

把线索中有关公司的信息转换到客户表中

把线索中有归案个人信息转换到联系人表中

把线索的备注信息转换到客户备份表中一份

把线索的备注信息转换到联系人备注表中一份

把线索和市场活动的关联关系转到联系人和市场活动的关联关系表中

 如果说客户有很明确的购买意向,本来是又高级销售继续跟进

但是在中级销售负责时就有超级强烈购买欲

则可以直接为客户创建交易

当然这种情况少,但客户如果有这样需求

 

 

 

简单说就是,线索表进行拆分,公司信息分到客户表,

个人信息分到联系人表,另外将备份信息

转到客户表备份表和联系人备份表  还有交易备注各一份

另外将原先市场活动和线索的对应关系转为市场活动和联系人的关系

 而最后交易表是由最高级的销售去谈的

 通过交易名点击到详情

 里面也有该交易的历史备注

因此如果需要创建交易,还需要把该线索下所有备注往交易备注表中存一份

转换的操作一定放在同一个事务中进行

思路:

用户在线索明细页面,点击线索转换按钮,跳转到详情页面

因为需要查询线索详情,需要携带线索id传过来

也就是需要内部跳转,跳转同时Controller查询具体的数据,放到作用域,然后转发

流程图

 那就先这么来做

Mapper

.XML

Service

 Impl

直接控制器里调用就行

 然后将作用域的数据取出,放到前端页面上

 

 这样作用域里需要赋值到页面的事做完了

 其实还有一个,点击为客户创建交易的话需要个下拉列表

那么Controller传值过来时也应该带上

Controller改一下

数据字典里Mapper去查

 .XML

 Service

Impl

Controller

 将数据字典这个list在前端遍历

页面差不多完成了,只要前端发请求

就是用户在线索明细,点击转换按钮时发请求

然后在JS入口函数里加单击事件

 发送同步请求的三种方式

url地址栏

form表单

a标签

 

 测试

 

接下来要做的事,进行搜索查询,选择对应的市场活动,将市场活动名字显示到上面

这个功能和线索关联市场活动差不多

注意线索关联市场活动可以多选,但这里只能选择一个,

选完之后(单选),拿到市场活动名字和id,将市场活动名字写道框里面 id放到隐藏域里面

点击转换的时候将市场活动id传回去保存到交易表

先做该功能

其实要搜索出来的市场活动应该是该包含该名字的市场活动且对应线索关联过的市场活动

参数就是市场活动名字和线索id  

就是要模糊查询名字包含XXXX的市场活动并且 去市场活动和线索的关联表中查与该线索关联的市场活动

Mapper

.xml

Service

IMPL

Controller

前端发请求,处理响应

给按钮加单击事件

把原先的弹出窗口代码去掉,原因我们做初始化工作做不了

 

加上id 

在JS入口函数中添加,点击弹出搜索框事件

 添加搜索框鼠标弹起发起请求事件

给搜索框加id

动态拼接 给动态拼接加个id

测试   将this. value()括号去掉

原先表名写错了

 

查了半天   id=""tBody

......

 

 存在的问题就是再次点开  前面的数据还在

需要做些初始化功能

 用户选择市场活动,将市场活动名字显示在输入框框中,id放到隐藏域中,然后关闭搜索市场活动窗口

给所有redio加单击事件,而这些按钮是动态的  需要用on

添加隐藏域

 

 测试

 下面将初始化清空下

下面做这个功能

用户点击转换按钮发送ajax异步请求,做下面这些事

提交的参数线索id(clueId)     交易表单中的参数   是否创建交易radio的状态

1.把线索中有关公司的信息转换到客户表中

那么先用线索id查出线索数据 将公司信息拿出来 一般会用客户实体类接收 insert到客户表中

2.把该线索中有关个人的信息转换到联系人表中

用线索id查出线索数据 将个人信息拿出来

3.把线索下所有备注信息转换到客户备注表中一份

用线索id查出所有备注  插入客户备注表

4.把线索下所有备注信息转换到联系人备注表中一份

用线索id查询所有备注  插入联系人备注表

5.把该先所有和市场活动的关联关系转换到联系人和市场活动的关联关系表中

用线索id查出所有关联的市场活动  

6.如果需要创建交易,则往交易表中添加一条记录

查看checkbox状态 选中需要为客户创建交易将交易表单的数据插入交易表

7.如果需要创建交易,则还需要把该线索下所有备注转换到交易备注表中一份

用线索id查线索备注,将查出的线索备注转到交易备注表中

8.删除该线索下所有的备注(一般做逻辑删除)

9.删除该线索下所有市场活动的关联关系

10.删除该线索

上面这些操作需要在同一个事务中进行,成功则都成功,失败则全部失败,以前返回影响记录条数,

然后在Controller通过记录条数来确定成功或者失败,而这里执行的太多,返回记录条数已经没用了,执行的sql太多,那么我们这里就用void,那么如何确定成功或者失败??在Controller里try catch抛异常失败,没有异常则成功

这里先做第一步,讲第一步的XML  Mapper  SERVER  IMPL  controller调通,在外成其他

1.把线索中有关公司的信息转换到客户表中

那么先用线索id查出线索数据 将公司信息拿出来 一般会用客户实体类接收 insert到客户表中

 

 客户表实体类还没写过,用Mybatis逆向工程生成

 

 

 对应的Mapper接口和XML文件还有实体类

 

 

 写Service

IMPL

 Controller

接着写前端,发请求,收集参数,表单验证

用户点击转换按钮发送请求,先给转换按钮添加单击事件

 有几处写错了

session写错名  几个字段

 

 

那么测试算是调通了

 

 

 转换成功跳转

 因为我们这里只做了一部分,正常老说转换成功跳转到主页面,那么该条线索也就没了

客户表数据也插入成功

下面做把线索中有关个人的信息转换到联系人表中

先用mybatis逆向工程生成联系人的Mapper,XML和实体类

ServerImpl

接下来根据clueId查询线索下所有备注

这个原先是写过的,但是查出来的是名字不是id因此重写一个

 

先用mybatis逆向工程生成客户备注的实体类,MAPPER,.XML

 

 

 

 

 弄错了  上面是联系人备注  反正一会也要生成

用mybatis逆向工程生成客户备注的实体类,Mapper,XML

 

 

 

 

6.把该线索下所有备注转换到联系人备注表中一份(批量保存)

 

 

7.根据clueId查询该线索和市场活动的关联关系

 根据clueId去线索和市场活动中间表中查

 

 

8.把该线索和市场活动的关联关系转换到联系人和市场活动的关联关系表中

用mybatis逆向工程生成联系人和市场活动中间表的实体类,MAPPER,XML

 

 

批量保存联系人和市场活动关联关系

 

 

9.如果需要创建交易,则往交易表中添加一条记录

用mybatis逆向工程生成交易表的实体类,MAPPER,XML

 

 

10.如果需要创建交易,需要把该线索下所有的备注转换到交易备注表中一份

用mybatis逆向工程生成交易备注表的实体类,MAPPER.XML

11.删除该线索下所有的备注

删数据顺序,先删子表记录,再删父表记录

 

 

12.删除该线索和市场活动的关联关系

 去就是线索和市场活动中间表中删除所有与该线索关联的数据

 

13.删除该线索

 

 

 删除客户表里记录  重新测试下

逻辑比较多  不管了,那么个意思,自己这边测试线索也已经删除了

接下来做查看交易明细

先做创建交易明细,

大概流程:

用户点击左边交易菜单,跳到交易列表,其他都不做了,就做创建交易,然后成功的话回到交易主页面然后点击交易名称跳转到具体交易明细阶段

创建交易

用户在交易主页面,点击创建按钮,跳转到创建交易的页面

用户在创建交易的页面填写表单,点击保存完成创建交易功能

所有者,阶段,类型,来源都是动态的

市场活动源是可以搜索的

联系人也是可以搜索的

可能性是可以配置的

客户名称支持自动补全

表单验证

保存成功跳转到交易列表主页面

保存失败提示信息页面不跳转

以上在做时,有些重复功能固定写死了

用户点击交易菜单,1跳到交易列表页面(控制器内部跳转)

2.查出交易类型和交易来源 阶段(控制器跳转的时候作用域传过来)

将交易(商机主页面)改成JSP

 

原先写死了

 类型原先也写死了

 

来源也改成动态的

 

 当用户在左侧菜单点击交易(商机),发送同步请求,跳转到交易列表

改一下

 

用户点击创建按钮 ,跳转到创建交易页面(同步请求)

通过Controller跳转,并且通过作用域传值

创建交易表里有所有者字段:需要查询所有用户信息

那么还有查询类型,查询来源  阶段

将需要的数据放到作用域中

 

 

下面就是用户点击创建按钮  请求控制器,控制器作用域传值转发到save.jsp页面

id加错位置了  放到BUTTON按钮里

 

让用户提供根据销售经验,我们整个配置文件(配置文件作用就是不改代码,即使是客户也可以教他如何配置),根据上面的各个阶段选择,来自动填充可能成交的概率

就是读取配置文件(原先配置的都是框架的配置文件,框架去读)

现在配置我们自己的配置文件

因为配置文件在后台,当用户每次选择阶段,都应该向后台发送请求获取数据

后台提供Controller接受和处理请求,根据选择的阶段,解析配置文件,获取对应的可能性

把获取的可能性返回前台页面显示在输入框

以前常用的配置文件.properties或是.XML

即使后台放的是Excel,PDF,text等文件也可以解析

配置文件:

A)XXX.PROPERTIES文件      key:value形式   适合配置简单数据 一一对应  几乎没有冗余数据 解                                                  析效率高(分布式时候不同服务间相互传递数据) 解析相对简单

                                                用IO里的Properties(以流形式相对麻烦)工具类解析,后期用ResourceBundle工具类解析

B)XXX.XML文件 XML可扩展标记语言  配置相对复杂数据(由XML语法决定,里面标签里套标签标签里放属性)比如配置一个学生信息

<studentList>

<student email="zs@163.com">

        <id>1001</id>

        <name>zs</name>

        <age>20</age>

</student>

<student email="ls@163.com">

        <id>1002</id>

        <name>ls</name>

        <age>20</age>

</student>

</studentList>只能有一个根标签,无论多复杂都可以配置出来。缺点:产生冗余数据,解析时占内存,效率低,解析相对复杂  常用解析XML技术     dom4j     jdom

其实就是选方便的,方便你方便客户就行了(一般用.propertis或.XML)

这里我们选用.properties文件   key对应value   阶段对应可能性

比如约定好用possibility.properties  放在resource目录下(其实该文件可以通过用户上传,或是指定目录读取等等方式,这里就规定放在resource目录下)

查各个阶段 

 

 

 

这样配置文件写好了, 接下来就是当客户在页面选择阶段时候

给后台发送请求,根据选择的阶段的值来找对应value

因为不用跳转页面,发送异步请求

后台解析就这么简单 

下面做前端  用户在阶段下拉选一选择  就向后台发请求   就是个changge事件

测试  报错

 打断点

原因:

ResourceBundle从properties文件读出来的编码是ISO-8859-1的编码   而后端接受到的是UTF编码

 OK了

给可能性加个禁止编辑  readonly

 

下面做客户名称自动补全,意思是当客户输入客户名称(公司名称)客户不能完全记住公司名称,当客户输入大概的公司名称,当键盘弹起,向后台模糊查询

然后以下拉选形式让客户选择具体的公司名,自动补全客户名称

而客户名称实际是个输入框,让查询出来的数据以下拉选形式展示

需要用到一个前端插件,自动补全插件

用BootStarp的一个自动补全插件:bs_typeahead

前端插件的使用步骤:

1)引入开发包:    .css    .js

2)创建容器(用来放插件得到的结果):常用的<div> 或者<input type="test">

3)当容器加载完成之后对容器调用工具函数:研究它的工具函数参数(一般官网文档查看)

在webapp下创建typeaheadtest.jsp用来演示自动补全插件

将jstl和上下文引进来

 引入开发包

 第一步引入开发包做完了  如下图

第二步创建容器  这里就用<input type="text">作为容器

第三步,对容器调用工具函数    source的数据来自数据源,这里就先写死

启动程序测试,因为这个页面没有在WEB-INF里 可以不登录直接访问

上面写死了,  实际应该是输入框键盘弹起  去查source数据源,source的数据源是动态的时候它有个参数,再写个测试

拷贝下

这个数据源可以是动态的,是个函数,函数里有两个参数

 

 

那么就发送异步请求去查  大概思路看注释    因为我们现在测试,传到后台的参数就先不加了

 因为现在测试,传到后台的参数就先不加了

下面开始写后台Controller

创建客户业务层

 持久层没有该方法就写一个

 返回的是系统类 可以转小写

测试 测试时确保客户表里有信息

 现在数据库也只有这一条数据

这里注意下第二个参数jquery的作用

其实第二个参数真正作用是这样的  它就是你在容器中输入的关键字

 

 

测试  这样数据量一大效率就高了

测试完成   接下来改造   第一步  引入有开发包   即插件

 第二部  创建容器   input输入框已经有了

第三步调用工具函数

 测试

 那么自动补全功能就做完了

但是从需求上讲  如果该公司确实没有    那么只能手动输入公司全名,然后先保存起来,然后在去创建一条插入到tbl_customer 那胡乱写呢? 这其实算了BUG  正规比如调用quan国gong商局系统的接口,是不是正规的一家  一般调用第三方接口

下面做保存创建的交易  因为可能有多个插入  返回哪个???这种情况,可以不返回

点击保存发送异步请求,参数是表单所有参数

 

 

后端保存交易做完了

接下来前端  发请求  有参收集参数 需要校验就校验  处理响应

给保存按钮添加id 然后加单击事件

 

 

 

点击保存成功

下面做查看交易明细

需求是创建交易后会在tbl_tran_history历史表里  每一个阶段都会在历史表里做记录

那么保存交易事实商做的是不完整的  那么去创建交易的Service里调用历史Mapper

往历史表里添加一条数据  记录交易历史

先用mybatis逆向工程创建交易历史表的XML和mapper接口  实体类

 

 

 

然后去保存创建的交易的业务实现类里添加交易历史

删掉上面两张表里原先保存交易的记录 重新测一遍

 

 插入历史.XML写错了

 

这样创建交易的整个流程才算真正做完

下面做查看交易明细

用户在交易主页面点击交易名称的超级连接  向后台发同步请求,参数是交易id,查出所有对应的数据

对应 交易基本信息 备注信息  交易历史信息

跳转到交易明细页面

查交易基本信息

 

 

 

查交易备注信息

 

 

查交易历史信息

 controller

接着是前端页面

先在该页面将作用域里的数据取出来

 

 

其实可能性也是Tran实体类中的一个属性,还可以这么做

 

 上面两种方式反正都可以  随意

上面这些,就把基本信息给显示完了

下面就是交易关联的备注信息显示  循环备注的几个div

先把美工写死的注释掉

  

 

 这样交易关联备注展示完了

下面展示该交易的历史阶段   历史阶段是个table表格

 

 

该展示的值都放上去了  现在就是从交易列表点击名字进去,这里就写死一条了

 

 测试    点进去之后

啊哦

查呗

 这里直接复制了.........应该是哪个</标签写错了或者没有注释

 排序改一下

 现在就是图标

这些图标需要用代码实现:分析下

交易阶段的图标

用图标显示阶段,那么图标数量和交易阶段的数量是一致的(每一个阶段对应显示一个图标)

图标的种类:这里有3类图标(不同款式)

图标颜色:这里有绿色和黑色

图标的顺序:跟阶段的顺序一致

图标的数量:正常会在数据字典维护中添加,所以图标的数量也可能变化

图标的实现:美工已经做好了  如下图  并非图片而是<span>标签  <span class="glyphicon">加上该class   bootstrap框架就会显示成图标

再加个class决定显示哪种图标<span class="glyphicon glyphicon-ok-circle">

glyphicon-map-circle

glyphicon-record-circle

等等  下图的 mystage这里我们没有用到  颜色就用style="color:xxxxx"

下图的属性 data-content就是bootstrap自定的属性  鼠标悬停时候展示的文字

   

下面开始做动态显示交易图标

总共几个阶段我们可以去数据字典表去拿

select value from tbl_dic_value where type_code="stage" order by order by order_no

首先:

1.按照顺序查询交易所有的阶段,得到一个交易应该有的顺序阶段   stageList

遍历stageList,显示每一个阶段对应图标,因为查的时候有顺序,显示时候也是有顺序的

 

 首先当前交易在哪个阶段是可以确定的

那么只需要比较是在当前交易前面还是在当前交易后面,在前面显示什么样的图标,在后面显示什么样的图标就行了

查的时候把orderNo也查出来 

 

 测试

其实还可以让图标动态修改交易的阶段  比如点击后面的阶段   发请求,将阶段修改

或者点击前面的阶段   让阶段往回返   同时修改交易历史 这里就不做了

最后做个统计图标功能

统计图表:以更专业,更形象的形式展示系统中的数据

这里我们只做一个交易表中的漏斗图(一般做为销售行业的统计)

展示各个阶段的交易量  

 报表插件:jfreechart  用起来不方便     iReport (支持前端与后端,调用打印等)收费,有免费的但免费的只支持基础部分,国产的有锐浪

Apache echars

Apache ECharts

使用插件步骤:

1.引入开发包:.JS,.CSS,拷贝到webapp目录下

2.创建容器:比如<input  type="text"><div>

3.当容器加载完成之后,对容器调用工具函数:插件的函数调用

那么我们就一步一步来

看文档示例

1.引入

2.创建容器

3.调用工具函数

 结果

按上面的先做个测试案例

在webapp下新建个echartstest.jsp

创建容器  直接将示例拷贝过来

当容器加载完成之后对容器调用工具函数

先别管什么意思  测试看能不能出来

 

 

 

修改type

用户点击交易统计图标, 向后端Controller发送同步请求

跳转到交易统计图标页面 transaction/index.jsp

当页面加载完成,向后台发送查询数据异步请求,

先做点击菜单,跳转到交易统计图表页面

 

前端

 

先跳转到测试页面试试

 

 

 

上面这些是假数据,现在将真实数据查询出来

定义返回的数据类

 

 

 

 

 

 前端  收集参数  校验  发送请求

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    String basePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/";
%>
<html>
<head>
    <base href="<%=basePath%>">
    <title>演示echarts插件</title>
    <%--引入jquery--%>
    <script type="text/javascript" src="jquery/jquery-1.11.1-min.js"></script>
    <%--引入echarts开发包--%>
    <script type="text/javascript" src="jquery/echars/echarts.min.js"></script>

    <script type="text/javascript">
        $(function (){
            //发送ajax查询数据请求
            $.ajax({
                url:'queryCountOfTranGroupByStage',
                type:'post',
                dataType:'json',
                success:function (res){
                    //当容器加载完成之后,对容器调用工具函数
                    // 基于准备好的dom,初始化echarts实例
                    var myChart = echarts.init(document.getElementById('main'));
                    // 使用刚指定的配置项和数据显示图表。
                    myChart.setOption({
                        title: {
                            text: 'Funnel'
                        },
                        tooltip: {
                            trigger: 'item',
                            formatter: '{a} <br/>{b} : {c}%'
                        },
                        toolbox: {
                            feature: {
                                dataView: { readOnly: false },
                                restore: {},
                                saveAsImage: {}
                            }
                        },
                        legend: {
                            data: ['Show', 'Click', 'Visit', 'Inquiry', 'Order']
                        },
                        series: [
                            {
                                name: 'Expected',
                                type: 'funnel',
                                left: '10%',
                                width: '80%',
                                label: {
                                    formatter: '{b}Expected'
                                },
                                labelLine: {
                                    show: false
                                },
                                itemStyle: {
                                    opacity: 0.7
                                },
                                emphasis: {
                                    label: {
                                        position: 'inside',
                                        formatter: '{b}Expected: {c}%'
                                    }
                                },
                                data: res
                            },
                        ]
                    });
                }
            })

        })
    </script>
</head>
<body>
<!-- 为 ECharts 准备一个定义了宽高的 DOM -->
<div id="main" style="width: 600px;height:400px;"></div>
</body>
</html>

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hrui0706

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值