网站首页> 博客> 基于SSM框架CRM客户管理系统

基于SSM框架CRM客户管理系统

好文 859
mwh
mwh 2022-01-03
收藏

1、概述

  • 开发环境

       IDE:    Myeclipse

        Jdk:   1.8

   数据库:   MySQL


演示地址:CRM客户关系管理系统


账号:admin

密码:123456


1.1 目的

      客户关系管理系统通过对客户生命周期的有效管理,帮助企业有效管理客户资源、控制销售过        程、缩短销售周期、提高销售成功率;通过对客户相关信息的分析与挖掘,识别客户消费规律        和客户价值,指导企业的部门运作和市场规划,从而提供更加快捷和周到的优质服务,帮助企        业提升客户满意度和忠诚度,最终提高企业市场竞争力。

2、任务概述

2.1 开发背景

     伴随着CRM客户管理系统在国内的飞速发展,绝大部分的企业都在应用CRM系统来服务自个         的企业,而且取得显著效果CRM系统强调建立一个以客户为中心的现代企业,以客户价值来       判定市场的需求,满足了将企业的战略从以产品为中心转向以客户为中心的企业需求。应用           CRM最终达到提高企业客户的满意度,减少客户的流失。

3、系统简介

     CRM客户关系管理是企业通过提供创新式的个性化客户服务为招揽新客户、保留旧客户、更好       的提供客户服务以及进一步培养企业和客户之间的关系、提升客户忠诚度的利用信息技术来协       调公司与客户间的销售、服务的营销管理方式。

4、业务需求描述

业务类别

业务

业务详解

基础模块

用户登录

用户登录

退出

退出当前登录用户

记住密码

记住登录用户的账号密码

密码修改

修改密码

...

其他

营销管理模块

营销机会管理:

企业客户的质询(询问)需求所建立的信息录入功能

客户开发计划

开发计划是根据营销机会而来,对于企业质询的客户,

会有相应的销售人员对于该客户进行具体的沟通交流,此时对于整个

CRM系统而言,通过营销开发计划来进行相应的信息管理,提高客户

的购买企业产品的可能性。

客户管理模块

客户信息管理

CRM系统中完整记录客户信息来源的数据、企业与

客户交往、客户订单查询等信息录入功能,方便企业与客户进行相应

的信息交流与后续合作。

客户流失管理

CRM通过一定规则机制所定义的流失客户(无效客

户),通过该规则可以有效管理客户信息资源,提高营销开发的效率。

服务管理模块

服务管理是针对客户而开发的功能,针对客户要求,CRM提供客户

相应的信息质询,反馈与投诉功能,提高企业对于客户的服务质量。

服务管理是针对客户而开发的功能,针对客户要求,CRM提供客户

相应的信息质询,反馈与投诉功能,提高企业对于客户的服务质量。

文件中心模块

上传合同文件

文件格式要求是PDF文件,其他文件不能上传

查询文件

查询所有文件,或根据文件标题进行模糊查询

下载文件

文件需要提供下载功能

系统公告模块

发布公告

发布系统公告

删除公告

删除公告

公告列表

列出全部公告

5、业务描述

5.1 基础模块

(一)登录

1)业务描述

      提供用户登录的功能,登录之后,可以进入主菜单进行操作其他菜单

2)输入

账号

不低于4位的英文单词组成

密码

长度不低于6位,并且要有英文大写字母

验证码

先识别验证码是否正确

 3)输出

       登录成功,进行页面跳转,跳转到主页面

       登录失败,进行页面的跳转,跳转到登录页面,并且提示登录失败的原因

4)业务流程

  1. 访问登录页面
  2. 在页面中输入用户名,密码信息,点击登录的按钮,提交数据
  3. 服务器接收到客户端的请求数据,去查询数据库
  4. 查询数据库之后,根据查询的结果响应前端
  5. 前端接受客户端的相应,并给出页面跳转到的操作

(二)退出

     1)请求 删除用户登录数据

     2)成功跳转到登录页面

(三)记住登录

      记住当前登录用户的登录账号和密码

     1)加密用户数据

     2)保存到session

(四)修改密码

       修改用户的登录密码

      1)验证输入用户老密码是否正确

      2)检查新密码是否符合规则

      3)请求 修改

5.2 营销管理模块

(一)营销机会管理

     增:

      1)前端js验证数据是否符合规则

      2)提交数据到后端进行添加处理

      3)失败 成功提示并刷新数据

      删:

     1)根据索引删除数据

      改:

     1)验证数据是否符合规则

     2)根据索引修改数据

       查:

     1)分页查询数据列出

            企业客户的质询(询问)需求所建立的信息录入功能

(二)客户开发计划

开发计划是根据营销机会而来,对于企业质询的客户,

会有相应的销售人员对于该客户进行具体的沟通交流,此时对于整个

CRM系统而言,通过营销开发计划来进行相应的信息管理,提高客户

的购买企业产品的可能性。

5.3 客户管理模块

(一)客户信息管理

增:

            1)验证客户数据是否符合规则

            2)符合请求数据添加

 删:

            1)根据索引请求后端进行删除

 改:

           1)根据索引修改数据 请求前先前端验证

 查:

1)分页查询数据列出

CRM系统中完整记录客户信息来源的数据、企业与客户交往、客户订单查询等信息录入功能,方便企业与客户进行相应的信息交流与后续合作。

(二)客户流失管理

            CRM通过一定规则机制所定义的流失客户(无效客户),通过该规则可以有效管 理客户                信息资源,提高营销开发的效率。

           5.4 服务管理模块

      服务管理是针对客户而开发的功能,针对客户要求,CRM提供客户相应的信息质询,          反馈与投诉功能,提高企业对于客户的服务质量。

5.5 文件中心模块

      (一)上传合同文件

            1)验证文件格式是否是pdf

            2)检测文件大小是否符合要求

       1、1 上传请求

    文件格式要求是PDF文件,其他文件不能上传

       1、2  查询文件

           1)根据用户选择的查询类型进行模糊查询 (上传时间,上传文件名)

           2)查询所有文件,或根据文件标题进行模糊查询

       1、3 下载文件

           1)下载请求

           2)输出文件下载

3)文件需要提供下载功能

5.6 系统公告模块

       1、1  添加公告

           1)验证公告数据是否符合规则

           2)请求后端添加

       1、2  删除公告

           1)根据索引进行删除

       1、3  公告列表

          1)分页查询数据列出

6、项目部分代码

aop日志:

package com.crm.aop;


import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.crm.pojo.Log;
import com.crm.pojo.SessionUser;
import com.crm.service.LogService;

@Aspect //该标签把LoggerAspect类声明为一个切面
@Order(1) //设置切面的优先级:如果有多个切面,可通过设置优先级控制切面的执行顺序(数值越小,优先级越高)
@Component //该标签把LoggerAspect类放到IOC容器中
public class LoggerAspect {
@Autowired
private LogService logService;
@Autowired
private HttpServletRequest request;

/**
* 取aop返回的json文本的对应值内容
* */
private String JSONAOP(String json,String path) {
int start=json.indexOf(path);
int end=json.indexOf(",", start);
if(end==-1) {end=json.length()-1;}//last
return json.substring(start+path.length()+1,end);//+1 =
}

/**
* 快速生成一个log
* */
private Log getLog(String Type,String Content,String userId) {
Log log= new Log();
log.setIp(request.getRemoteAddr());
log.setTime(new Date());
log.setType(Type);
log.setContent(Content);
log.setUserId(userId);
return log;
}


/**
* 定义一个方法,用于声明切入点表达式,方法中一般不需要添加其他代码
* 使用@Pointcut声明切入点表达式
* 后面的通知直接使用方法名来引用当前的切点表达式;如果是其他类使用,加上包名即可
*/
@Pointcut("execution(public * com.crm.controller.*Controller.*(..))")
public void declearJoinPointExpression(){}

/**
* 前置通知
* @param joinPoint
*/
@Before("declearJoinPointExpression()") //该标签声明次方法是一个前置通知:在目标方法开始之前执行
public void beforMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List args = Arrays.asList(joinPoint.getArgs());
System.out.println("前置通知:this method "+methodName+" begin. param<"+ args+">");
}
/**
* 后置通知(无论方法是否发生异常都会执行,所以访问不到方法的返回值)
* @param joinPoint
*/
@After("declearJoinPointExpression()")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("后置通知:this method "+methodName+" end.");
}
/**
* 返回通知(在方法正常结束执行的代码)
* 返回通知可以访问到方法的返回值!
* @param joinPoint
*/
@AfterReturning(value="declearJoinPointExpression()",returning="result")
public void afterReturnMethod(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
String method=request.getMethod();
String controllerName=joinPoint.getTarget().getClass().getName().substring(joinPoint.getTarget().getClass().getName().lastIndexOf(".")+1, joinPoint.getTarget().getClass().getName().length()) ;
if(methodName.equals("login") && method.equals("POST")) {//登录
HttpSession session = request.getSession();
SessionUser user = (SessionUser)session.getAttribute("SUser");
Log log=getLog("用户登录","Method:"+methodName+",Data:<"+result+">,Result:"+JSONAOP(result.toString(),"type"),"{\"userId\":"+user.getId()+",\"type\":\""+user.getRoleName()+"\"}");
if(logService.add(log)>0) {
System.out.println("{\"LogStatus\":\"OK\",\"类型\":\"返回通知 Method:"+methodName+"\",\"end.result\":\"<"+result+">\"}");
}else {
System.out.println("{\"LogStatus\":\"No\",\"类型\":\"返回通知 Method:"+methodName+"\",\"end.result\":\"<"+result+">\"}");
}
}else if(methodName.equals("ApiAdd") && method.equals("POST")) {
System.out.println("数据添加"+controllerName);
}else {
System.out.println(controllerName);
}
}
/**
* 异常通知(方法发生异常执行的代码)
* 可以访问到异常对象;且可以指定在出现特定异常时执行的代码
* @param joinPoint
* @param ex
*/
@AfterThrowing(value="declearJoinPointExpression()",throwing="ex")
public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
String methodName = joinPoint.getSignature().getName();
Log log=getLog("系统异常","Method:"+methodName+" end.ex message<"+ex+">","{\"userId\":0,\"type\":\"SYSTEM\"}");
if(logService.add(log)>0) {
System.out.println("{\"LogStatus\":\"OK\",\"类型\":\"异常通知 Method:"+methodName+"\",\"ex message\":\"<"+ex+">\"}");
}else {
System.out.println("{\"LogStatus\":\"No\",\"类型\":\"异常通知 Method:"+methodName+"\",\"ex message\":\"<"+ex+">\"}");
}
}
/**
* 环绕通知(需要携带类型为ProceedingJoinPoint类型的参数)
* 环绕通知包含前置、后置、返回、异常通知;ProceedingJoinPoin 类型的参数可以决定是否执行目标方法
* 且环绕通知必须有返回值,返回值即目标方法的返回值
* @param point
*/
@Around(value="declearJoinPointExpression()")
public Object aroundMethod(ProceedingJoinPoint point){
Object result = null;
String methodName = point.getSignature().getName();
try {
//前置通知
System.out.println("环绕通知/前置通知: The method "+ methodName+" start. param<"+ Arrays.asList(point.getArgs())+">");
//执行目标方法
result = point.proceed();
//返回通知
System.out.println("环绕通知/返回通知:The method "+ methodName+" end. result<"+ result+">");
} catch (Throwable e) {
//异常通知
System.out.println("环绕通知/异常通知:this method "+methodName+" end.ex message<"+e+">");
throw new RuntimeException(e);
}
//后置通知
System.out.println("环绕通知/后置通知:The method "+ methodName+" end.");
return result;
}
}


登录控制器 :

package com.crm.controller;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


import javax.imageio.ImageIO;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import com.crm.pojo.Marketer;
import com.crm.pojo.SessionUser;
import com.crm.pojo.WebSystem;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.crm.pojo.Admin;
import com.crm.pojo.IRole;
import com.crm.service.AdminService;
import com.crm.service.CustomerProjectService;
import com.crm.service.CustomerService;
import com.crm.service.IRoleService;
import com.crm.service.MarketerService;
import com.crm.service.NoticeService;
import com.crm.service.OrdersService;
import com.crm.service.SystemService;
import com.crm.util.CpachaUtils;
import com.crm.util.EncryptionUtil;
import com.crm.util.Interceptor;

import com.crm.util.JsonUtil;
import com.crm.util.Permission;



/**
* 系统主页控制器
* @version
*/
@Controller
@RequestMapping("/system")
public class SystemController {
@Autowired
private MarketerService marketerService;

@Autowired
private AdminService adminService;

@Autowired
private SystemService systemService;

@Autowired
private IRoleService roleService;

@Autowired
private CustomerService customerService;

@Autowired
private CustomerProjectService customerProjectService;

@Autowired
private NoticeService noticeService;

@Autowired
private OrdersService ordersService;

@RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView login(ModelAndView model,HttpServletRequest req) {
ServletContext application=req.getServletContext();
WebSystem web=(WebSystem) application.getAttribute("WebSystem");
SessionUser user = (SessionUser) req.getSession().getAttribute("SUser");
if (user != null) {
try {
if(new Interceptor().toeknVer(user)) {//验证成功给它自动跳转到首页去
if(web!=null) {
model.setView(new RedirectView( "/system/index", true, false, true ));
model.addObject("token",EncryptionUtil.getMD5(user.getToken()));
return model;
}else {System.out.println("系统错误:存在非法注入");}
//resp.sendRedirect(req.getContextPath() + "/system/index");
}
} catch (UnsupportedEncodingException e) {
req.getSession().setAttribute("SUser", null);//token验证失败了直接给他清掉
}
}
Object obj=application.getAttribute("permissionData");
if(obj==null) {//初始化api权限
application=Permission.Initialize(application);
}
/**
* 写入网站数据 方便页面直接调用 主要是为了减少服务器资源占用 当然这样就相当于了一个缓存
* */
if(application.getAttribute("WebSystem")==null) {
application.setAttribute("WebSystem", systemService.getSystem());
}
model.setViewName("system/login");
return model;
}

@RequestMapping(value = "/index", method = RequestMethod.GET)
public ModelAndView index(ModelAndView model) {
model.setViewName("system/index");
return model;
}

@RequestMapping(value = "/reg", method = RequestMethod.GET)
public ModelAndView reg(ModelAndView model) {
model.setViewName("system/reg");
return model;
}

@RequestMapping(value = "/WebSet", method = RequestMethod.GET)
public ModelAndView WebSet(ModelAndView model) {
model.addObject("Web",systemService.getSystem());
model.setViewName("system/web_set");
return model;
}

@RequestMapping(value = "/welcome", method = RequestMethod.GET)
public ModelAndView welcome(ModelAndView model) {
Map retMap = new HashMap();
retMap.put("customerSize",customerService.getTotal(new HashMap()));
retMap.put("customerProjectSize",customerProjectService.getTotal(new HashMap()));
retMap.put("marketerSize", marketerService.getTotal(new HashMap()));
retMap.put("ordersSize", ordersService.getTotal(new HashMap()));
Map queryMap = new HashMap();
/**
* 倒着找数据 找最新的公告
* */
int noticeSize=noticeService.getTotal(queryMap);
if(noticeSize-10<0) {noticeSize=0;}else {noticeSize-=10;}
queryMap.put("limit", 10);
queryMap.put("offset",noticeSize);
retMap.put("noticeList",noticeService.findList(queryMap));
model.addObject("Data",retMap);
model.setViewName("system/welcome");
return model;
}

@RequestMapping(value = "/login_out", method = RequestMethod.GET)
public String loginOut(HttpServletRequest request) {
request.getSession().setAttribute("SUser", null);
return "redirect:login";
}

@RequestMapping(value = "/permission", method = RequestMethod.GET)
public ModelAndView permission(ModelAndView model,@RequestParam(name = "id", required = false) String id) {
if(id!=null && !id.equals("")) {
model.addObject("id",id);//取当前用户数据
model.setViewName("system/permission/permission");
}else {
model.setViewName("404");
}
return model;
}

@RequestMapping(value = "/permissionEdit", method = RequestMethod.GET)
public ModelAndView permissionEdit(ModelAndView model,
@RequestParam(name = "index", required = false) String index,
@RequestParam(name = "id", required = false) Long id) {
Map retMap = new HashMap();
IRole role= roleService.findById(id);
JSONObject jsonobj=(JSONObject) JSONPath.read(role.getInit(), "$"+index);
retMap=JsonUtil.jsonToMap(jsonobj);
retMap.put("id", id);
retMap.put("index",index);
model.addObject("permission",retMap);

model.setViewName("system/permission/permission_edit");
return model;
}

@RequestMapping(value = "/roleList", method = RequestMethod.GET)
public ModelAndView permissionList(ModelAndView model) {
model.setViewName("system/permission/role_list");
return model;
}
/**
* 系统清理接口
* */
@RequestMapping(value = "/ApiClear", method = RequestMethod.GET)
@ResponseBody
public Map ApiClear(HttpServletRequest req){
Map retMap = new HashMap();
ServletContext application=req.getServletContext();
application.setAttribute("WebSystem", systemService.getSystem());
retMap.put("code", 1);
retMap.put("msg","清理成功");
return retMap;
}

/**
* 系统logo上传
* @return Map
*/
@RequestMapping(value = "/ApiLogoUp", method = {RequestMethod.POST})
@ResponseBody
public Map ApiLogoImg(
@RequestParam(name="file", required = false) MultipartFile file,HttpServletRequest request,
@RequestParam(name="id", required = false) String id
){
String filepath = "";
//保存上传
OutputStream out = null;
InputStream fileInput=null;
Map map=new HashMap<>();
try{
if(file!=null){
filepath = request.getServletContext().getRealPath("/resources/logo.png") ;
filepath = filepath.replace("\\", "/");
File files=new File(filepath);
//打印查看上传路径
System.out.println(filepath);
if(!files.getParentFile().exists()){
files.getParentFile().mkdirs();
}
file.transferTo(files);
}else {
map.put("code",201);
map.put("msg","error:file=null,springmvc可能没有开启文件上传");
map.put("data","");
return map;
}
}catch (Exception e){
}finally{
try {
if(out!=null){
out.close();
}
if(fileInput!=null){
fileInput.close();
}
} catch (IOException e) {
}
}
map.put("code",0);
map.put("msg","");
map.put("data","/resources/logo.png" );
return map;
}

/**
* 系统设置
* */
@RequestMapping(value = "/ApiSetWeb", method = RequestMethod.POST)
@ResponseBody
public Map ApiSetWeb(WebSystem web,@RequestParam(name = "file", required = false) String file){
Map retMap = new HashMap();
if(systemService.update(web)>0) {
retMap.put("code", 0);
retMap.put("msg","修改成功!");
}else {
retMap.put("code", 201);
retMap.put("msg","修改失败!");
}

return retMap;
}

/***
* Api 编辑用户权限
*
* */
@RequestMapping(value = "/ApiPermissionUpdate", method = RequestMethod.POST)
@ResponseBody
public Map ApiPermissionUpdate(
@RequestParam(name = "id", required = false) Long id,
@RequestParam(name = "title", required = false) String title,
@RequestParam(name = "href", required = false) String href,
@RequestParam(name = "icon", required = false) String icon,
@RequestParam(name = "index", required = false) String index
){
Map retMap = new HashMap();
IRole role = roleService.findById(id);
JSONArray jsonArray= JSON.parseArray(role.getInit());
JSONPath.set(jsonArray, "$"+index+".icon", icon);
JSONPath.set(jsonArray, "$"+index+".title", title);
JSONPath.set(jsonArray, "$"+index+".href", href);
role.setInit(jsonArray.toString());

if(roleService.PermissionUpdate(role)>0) {
retMap.put("code",0);
retMap.put("msg","修改成功!");
}else {
retMap.put("code",201);
retMap.put("msg", "修改失败!");
}
return retMap;
}

/**
* Api用户权限状态
* */
@RequestMapping(value = "/ApiPermissionStatus", method = RequestMethod.POST)
@ResponseBody
public Map ApiPermissionStatus(
@RequestParam(name = "id", required = false) Long id,
@RequestParam(name = "index", required = false) String index){
Map retMap = new HashMap();
IRole role = roleService.findById(id);
JSONArray jsonArray= JSON.parseArray(role.getInit());

if(id!=null) {
if(JSONPath.read(role.getInit(), "$"+index+".status").equals("1")) {
JSONPath.set(jsonArray, "$"+index+".status","2");
}else {
JSONPath.set(jsonArray, "$"+index+".status", "1");
}
role.setInit(jsonArray.toString());
if(roleService.PermissionUpdate(role)>0) {
retMap.put("code",0);
retMap.put("msg","修改成功!");
}else {
retMap.put("code",201);
retMap.put("msg", "修改失败!");
}
}else {
retMap.put("code", 201);
retMap.put("msg", "id不能为空");
}
return retMap;
}


/**
* Api 取当前用户权限
* */
@RequestMapping(value = "/ApiGetPermissionMenu", method = RequestMethod.GET)
@ResponseBody
public Map ApiGetPermissionMenu(HttpServletRequest req){
Map retMap = new HashMap();
Map homeInfo = new HashMap();
Map logoInfo = new HashMap();
SessionUser user = (SessionUser) req.getSession().getAttribute("SUser");
WebSystem system=systemService.getSystem();

if(user!=null) {
IRole role= roleService.findById(Long.valueOf(user.getRole()));

homeInfo.put("href", "welcome");
homeInfo.put("title", "首页");

logoInfo.put("image", "../resources/logo.png");
logoInfo.put("href", "index");
logoInfo.put("title",system.getTitle());
String init=role.getInit();
JSONArray jsonArray = JSONArray.parseArray(init);
jsonArray=(JSONArray) JsonUtil.RemoveJsonStatus(jsonArray,"","status","2",jsonArray);
retMap.put("menuInfo",jsonArray);
retMap.put("logoInfo",logoInfo);
retMap.put("homeInfo",homeInfo);
}else {
retMap.put("msg","ApiGetPermission error!");
retMap.put("code","201");
}
return retMap;
}

/**
* Api 取用户编辑权限 json
* Permission 转树型数据json
* @param ID 角色id
* @return json
* */
@RequestMapping(value = "/ApiGetPermission", method = RequestMethod.GET)
@ResponseBody
public Map ApiGetPermission(@RequestParam(name = "id", required = false) String id,HttpServletRequest req){
Map retMap = new HashMap();
if(id!=null) {
IRole role= roleService.findById(Long.parseLong(id));
JSONArray jsonArray= JSON.parseArray(role.getInit());
JSONArray retjson= new JSONArray();
retjson=JsonUtil.JsonToMenuMap(jsonArray,"",retjson,0,0);
retMap.put("data",retjson);
retMap.put("code",0);
retMap.put("msg","success");
}else {
retMap.put("msg","ApiGetPermissionEdit error!");
retMap.put("code","201");
}
return retMap;
}


/**
* Api 角色列表
* */
@RequestMapping(value = "/ApiGetPermissionList", method = RequestMethod.POST)
@ResponseBody
public Map ApiGetPermissionList(){
Map retMap = new HashMap();

List role= roleService.findList();
if(!role.isEmpty()) {
retMap.put("code", 0);//状态码 layui中code为0才是成功 注意不是200
retMap.put("msg","success");//msg
retMap.put("data", role);// 数据
retMap.put("count",roleService.getTotal());// 获取符合结果的总记录数
}else {
retMap.put("code", "201");
retMap.put("msg","无数据");
}
return retMap;
}


/**
* Api 权限判断
* */
@RequestMapping(value = "/ApiIfPermission", method = RequestMethod.POST)
@ResponseBody
public Map ApiIfPermission(@RequestParam(value = "index", required = true) String index,HttpServletRequest req){
Map retMap = new HashMap();
SessionUser user = (SessionUser) req.getSession().getAttribute("SUser");
if(IfPermission(index,Long.valueOf(user.getRole()))) {//user.getRole()
retMap.put("code", 200);
retMap.put("msg","success");//msg
}else {
retMap.put("code", 201);
retMap.put("msg","error");
}
return retMap;
}


/**
* 判断是否拥有权限
* @param Str index 权限索引
* @param Long id 角色id
* */
public Boolean IfPermission(String index,Long id) {
IRole role= roleService.findById(id);
int status=(int) JSONPath.read(role.getInit(),index+".status");
if(status==1) {
return false;
}
return false;
}

/**
* 登录表单提交
*
* @param request
* @param username
* @param passwordd
* @param vcode
* @return
* @throws UnsupportedEncodingException
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public Map login(@RequestParam(value = "username", required = true) String username,
@RequestParam(value = "password", required = true) String password,
@RequestParam(value = "vcode", required = true) String vcode, HttpServletRequest request)
throws UnsupportedEncodingException {
Map retMap = new HashMap();
SessionUser SUser = new SessionUser();
if (StringUtils.isEmpty(username)) {
retMap.put("type", "error");
retMap.put("msg", "请输入用户名!");
return retMap;
}
if (StringUtils.isEmpty(password)) {
retMap.put("type", "error");
retMap.put("msg", "请输入密码!");
return retMap;
}
if (StringUtils.isEmpty(vcode)) {
retMap.put("type", "error");
retMap.put("msg", "请输入验证码!");
return retMap;
}
String loginCpacha = (String) request.getSession().getAttribute("loginCpacha");
if (StringUtils.isEmpty(loginCpacha)) {// session中验证码失效
retMap.put("type", "error");
retMap.put("msg", "长时间未操作,验证码已失效,请刷新验证码后重试!");
return retMap;
}
if (!vcode.toUpperCase().equals(loginCpacha.toUpperCase())) {
retMap.put("type", "error");
retMap.put("msg", "验证码有误,请重新输入!");
return retMap;
}
request.getSession().setAttribute("loginCpacha", null);
// 从数据库中查询用户
Marketer user = marketerService.findByMarketerName(username);
Admin admin = null;
if (user == null) {
admin = adminService.findByAdminName(username); //开始管理员验证
if(admin==null) {//都不存在
retMap.put("type", "error");
retMap.put("msg", "该用户不存在!");
return retMap;
}else if(!(password = EncryptionUtil.getMD5(password)).equals(admin.getPassword())) {//admin存在 判断admin密码
retMap.put("type", "error");
retMap.put("msg", "密码有误,请重新输入!");
return retMap;
}else {//admin正确
SUser.setRole(admin.getRole());
SUser.setRoleName("Admin");
SUser.setId(admin.getId());
}
}else if (!(password = EncryptionUtil.getMD5(password)).equals(user.getPassword())) {//营销员存在 判断营销员密码
retMap.put("type", "error");
retMap.put("msg", "密码有误,请重新输入!");
return retMap;
} else {//营销员正常
SUser.setRole(user.getRole());
SUser.setRoleName("Marketer");
SUser.setId(user.getId());
}
/**下面开始设置application内的权限json*/
ServletContext application=request.getServletContext();
Object obj=application.getAttribute("permission");
if(obj!=null) {//tomcat刚启动
@SuppressWarnings("unchecked")
Map permission = (Map)obj;
obj=permission.get(SUser.getRole());
if(obj!=null) {//obj为当前登录用户权限的json 该用户权限登录过
JSONArray permissionJson=(JSONArray)obj;
IRole role=roleService.findById(Long.valueOf(SUser.getRole()));
if(!JSONArray.parse(role.getInit()).toString().equals(permissionJson.toString())) {
System.out.println("权限有修改!");
permission.put(SUser.getRole(),JSONArray.parse(role.getInit()));
application.setAttribute("permission",permission);
}
}else {//该用户权限没登录过
IRole role=roleService.findById(Long.valueOf(SUser.getRole()));
permission.put(SUser.getRole(),JSONArray.parse(role.getInit()));
application.setAttribute("permission",permission);
}
}else {
Map permission = new HashMap();
IRole role=roleService.findById(Long.valueOf(SUser.getRole()));
permission.put(SUser.getRole(),JSONArray.parse(role.getInit()));
application.setAttribute("permission",permission);
}
/**设置结束*/

SUser.setUsername(username);
SUser.setPassword(EncryptionUtil.getMD5(password));
/**
* token令牌检测登录状态 主要原理就是将username和password进行rc4加密后保存到session
* 然后在拦截器中解密数据和当前session数据重新进行对比 由于rc4加密出来是二进制数据 无法直接输出 一般进行base64编码后保存
*/
SUser = new Interceptor().token(SUser);

request.getSession().setAttribute("SUser", SUser);
retMap.put("type", "success");
retMap.put("msg", "恭喜你,登录成功!");
retMap.put("herf", "index");
return retMap;
}

/**
* 显示验证码
* @param request
* @param response
* @param vl
* @param w
* @param h
*/
@RequestMapping(value = "/Captcha.png", method = RequestMethod.GET)
public void getCpacha(HttpServletRequest request, HttpServletResponse response,
@RequestParam(name = "vl", required = false, defaultValue = "4") Integer vl,
@RequestParam(name = "w", required = false, defaultValue = "150") Integer w,
@RequestParam(name = "h", required = false, defaultValue = "50") Integer h) {
CpachaUtils cpachaUtils = new CpachaUtils(vl, w, h);
String generatorVCode = cpachaUtils.generatorVCode();
request.getSession().setAttribute("loginCpacha", generatorVCode);
BufferedImage generatorRotateVCodeImage = cpachaUtils.generatorRotateVCodeImage(generatorVCode, true);
try {
ImageIO.write(generatorRotateVCodeImage, "gif", response.getOutputStream());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}


客户控制器:

package com.crm.controller;


import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


import org.apache.commons.fileupload.disk.DiskFileItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSONArray;
import com.crm.pojo.Customer;
import com.crm.service.CustomerService;
import com.crm.service.MarketerService;

import com.crm.util.ExcelUtil;

import jxl.read.biff.BiffException;

/**
* 客户控制器
*
* @date:2021年12月7日 下午4:16:06
*/
@Controller
@RequestMapping("/customer")
public class CustomerController {

@Autowired
private CustomerService customerService;

@Autowired
private MarketerService marketerService;

@RequestMapping(value = "/list", method = RequestMethod.GET)
public ModelAndView list(ModelAndView model) {
Map queryMap = new HashMap();
model.addObject("merkList", marketerService.findList(queryMap));
model.setViewName("customer/customer_list");
return model;
}

@RequestMapping(value = "/listDrain", method = RequestMethod.GET)
public ModelAndView listDrain(ModelAndView model) {
Map queryMap = new HashMap();
model.addObject("merkList", marketerService.findList(queryMap));
model.setViewName("customer/customerdrain_list");
return model;
}
@RequestMapping(value = "/importCustomer", method = RequestMethod.GET)
public ModelAndView importCustomer(ModelAndView model) {
Map queryMap = new HashMap();
model.addObject("merkList", marketerService.findList(queryMap));
model.setViewName("customer/customer_import");
return model;
}

@RequestMapping(value = "/updateDrain", method = RequestMethod.GET)
public ModelAndView updateDrain(ModelAndView model, @RequestParam(name = "id", required = false) Integer id) {
Map queryMap = new HashMap();
model.addObject("type", 1);// 前端参数类型 1表示修改
if (id != null) {
Customer customerdrain = customerService.findByCustomerId(id);
model.addObject("customerdrain", customerdrain);// 取当前用户数据
model.addObject("merkList", marketerService.findList(queryMap));
model.setViewName("customer/customerdrain_update");
} else {
model.setViewName("404");// 如未传递id直接404 因为在web.xml中设置了404 所以随便指定一个未存在页面即可 *注意并不是说serViewName(404)就代表了404的源地址
}
return model;
}

@RequestMapping(value = "/add", method = RequestMethod.GET)
public ModelAndView add(ModelAndView model) {
Map queryMap = new HashMap();
model.addObject("merkList", marketerService.findList(queryMap));
model.setViewName("customer/customer_add_update");
model.addObject("type", 2);// 前端类型 1是修改,2是添加
return model;
}

@RequestMapping(value = "/update", method = RequestMethod.GET)
public ModelAndView update(ModelAndView model, @RequestParam(name = "id", required = false) Integer id) {
Map queryMap = new HashMap();
model.addObject("type", 1);// 前端类型 1是修改,2是添加

if (id != null) {
Customer customer = customerService.findByCustomerId(id);
model.addObject("customer", customer);
model.addObject("merkList", marketerService.findList(queryMap));
model.setViewName("customer/customer_add_update");
} else {
model.setViewName("404");
}
return model;
}

@RequestMapping(value = "ApicustomerImportData", method = {RequestMethod.POST})
@ResponseBody
public Map ApicustomerImportData(
@RequestParam(name="marketerId", required = false) Integer marketerId,
@RequestParam(name="customerData", required = false) String data
) {
Map retmap=new HashMap<>();
if(marketerId!=null && marketerId!=0) {
JSONArray dataArr=(JSONArray) JSONArray.parse(data);
for(Object d:dataArr) {
JSONArray a=(JSONArray)d;
Customer customer=new Customer();
customer.setMarketerId(marketerId);
customer.setName(a.get(0).toString());
customer.setAddress(a.get(1).toString());
customer.setTel(a.get(2).toString());
customer.setMemo(a.get(3).toString());
customerService.add(customer);
}
retmap.put("code", 0);
retmap.put("msg", "导入结束");
}else {
retmap.put("code", 201);
retmap.put("msg", "请指定营销员");
}
return retmap;


}
/**
* 导入客户excel
* @return Map
* @throws Exception
*/
@RequestMapping(value = "/ApiImportExcel", method = {RequestMethod.POST})
@ResponseBody
public Map ApiImportExcel(@RequestParam(name="file", required = false) MultipartFile file) {
Map retmap=new HashMap<>();
if(file!=null) {
CommonsMultipartFile cf= (CommonsMultipartFile)file;
DiskFileItem fi = (DiskFileItem)cf.getFileItem();
File excelFile = fi.getStoreLocation();
try {
String[][] data=ExcelUtil.readSpecifyRows(excelFile);
retmap.put("code",0);
retmap.put("msg","解析成功");
retmap.put("data",data);
} catch (BiffException | IOException e) {
//e.printStackTrace();
retmap.put("code", 201);
retmap.put("msg","文件错误,请提交正确的Excel文件!");
}
}else {
retmap.put("code", 201);
retmap.put("msg","请上传文件");
}
return retmap;
}

/**
* 获取客户列表
*
* @param name
* @param marketerId
* @param status
* @param limit
* @param page
* @return
*/
@RequestMapping(value = "/ApiGetList", method = RequestMethod.POST)
@ResponseBody
public Map ApiGetList(@RequestParam(name = "name", required = false) String name,
@RequestParam(name = "marketerId", required = false) Integer marketerId,
@RequestParam(name = "tel", required = false) String tel,
@RequestParam(name = "limit", required = false) Integer limit,
@RequestParam(name = "page", required = false) Integer page,
@RequestParam(name = "status", required = false) Integer status
) {
Map queryMap = new HashMap();
Map retMap = new HashMap();
queryMap.put("name", name);
queryMap.put("marketerId", marketerId);
queryMap.put("tel", tel);
queryMap.put("offset", limit * (page - 1));// 对应数据库中的偏移量
queryMap.put("limit", limit * page);// 每页显示记录条数,也就是每页显示的数量
queryMap.put("status",status);//用户状态
List customer = customerService.findList(queryMap);
if (!customer.isEmpty()) {
retMap.put("code", 0);// 状态码 layui中code为0才是成功 注意不是200
retMap.put("msg", "success");// msg
retMap.put("data", customer);// 数据
retMap.put("count", customerService.getTotal(queryMap));// 获取符合结果的总记录数
return retMap;
} else {
retMap.put("code", "201");
retMap.put("msg", "无数据");
return null;
}
}

/**
* 添加
*
* @param name
* @param address
* @param marketerId
* @param tel
* @param status
* @param memo
* @return
*/
@RequestMapping(value = "/ApiAdd", method = RequestMethod.POST)
@ResponseBody
public Map ApiAdd(Customer customer) {
Map retMap = new HashMap();
if (customerService.add(customer) > 0) {
retMap.put("code", 0);
retMap.put("msg", "success");
} else {
retMap.put("code", 201);
retMap.put("msg", "error");
}
return retMap;
}
/**
* 删除
*
* @param ids
* @return
*/
@RequestMapping(value = "/ApiDelete", method = RequestMethod.POST)
@ResponseBody
public Map ApiDelete(@RequestParam(value = "ids[]", required = true) Long[] ids) {
Map retMap = new HashMap();
if (ids == null) {
retMap.put("type", "error");
retMap.put("msg", "请选择要删除的数据!");
return retMap;
}
String idsString = "";
for (Long id : ids) {// 把Long分割并转换为idsString这个是加强for循环,逐个遍历ids的每个元素 直到ids的最后一个遍历完 跳出循环,
// ids大于0,也就是有多个,就把这个ids赋给数组,并循环,在循环里去数组的值,进行多个操作
idsString += id + ",";
}
idsString = idsString.substring(0, idsString.length() - 1);// 截取掉字符串的最后一位
if (customerService.delete(idsString) <= 0) {// 文本框中内容非空时执行删除操作
retMap.put("type", "error");
retMap.put("msg", "删除失败!");
return retMap;
}
retMap.put("type", "success");
retMap.put("msg", "删除成功!");
return retMap;
}

/**
* 修改
*
* @param customer
* @param request
* @param address
* @param marketerId
* @param tel
* @param memo
* @return
*/
@RequestMapping(value = "/ApiUpdate", method = RequestMethod.POST)
@ResponseBody
public Map ApiUpdate(Customer customer,@RequestParam(value = "statusRaw", required = false) int statusRaw) {
Map retMap = new HashMap();// 输出集合
/**
* status代表status没有经过修改的值 这里有个注意的点 因为customer里面status的类型定义的是Integer 不是int 所以必须先判断是不是为空
* 否则一旦status没有传就会空指针
* Integer的默认值为null int默认值为0
* */
if(customer.getStatus()!=null && customer.getStatus()==0 && statusRaw==1) {
customer.setLostTime(new Date());
}
if (customerService.update(customer) > 0) {
retMap.put("code", 0);
retMap.put("msg", "success");
} else {
retMap.put("code", 201);
retMap.put("msg", "修改失败");
}
return retMap;
}
}


工具类:

package com.crm.util;


import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
/**
* 加密工具类
* @date: 2021年11月3日 下午8:00:16
* @version V1.0
*/
public class EncryptionUtil {
/***
* MD5加密
* */
public static String getMD5(String strMD5) {
try {
// 得到一个信息摘要器
MessageDigest digest = MessageDigest.getInstance("md5");
byte[] result = digest.digest(strMD5.getBytes());
StringBuffer buffer = new StringBuffer();
// 把每一个byte 做一个与运算 0xff;
for (byte b : result) {
// 与运算
int number = b & 0xff;// 加盐
String str = Integer.toHexString(number);
if (str.length() == 1) {
buffer.append("0");
}
buffer.append(str);
}

// 标准的md5加密后的结果
return buffer.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}
}
/**
* RC4加解密
* */
public static String HloveyRC4(String aInput,String aKey)
{
int[] iS = new int[256];
byte[] iK = new byte[256];
for (int i=0;i<256;i++)
iS[i]=i;
int j = 1;
for (short i= 0;i<256;i++)
{
iK[i]=(byte)aKey.charAt((i % aKey.length()));
}
j=0;
for (int i=0;i<255;i++)
{
j=(j+iS[i]+iK[i]) % 256;
int temp = iS[i];
iS[i]=iS[j];
iS[j]=temp;
}
int i=0;
j=0;
char[] iInputChar = aInput.toCharArray();
char[] iOutputChar = new char[iInputChar.length];
for(short x = 0;x {
i = (i+1) % 256;
j = (j+iS[i]) % 256;
int temp = iS[i];
iS[i]=iS[j];
iS[j]=temp;
int t = (iS[i]+(iS[j] % 256)) % 256;
int iY = iS[t];
char iCY = (char)iY;
iOutputChar[x] =(char)( iInputChar[x] ^ iCY) ;
}
return new String(iOutputChar);
}

/**
* base64编码
* @param string
* @return
* @throws UnsupportedEncodingException
*/
public static String Base64Encoder(String string) throws UnsupportedEncodingException {
Encoder encoder = Base64.getEncoder();
byte[] textByte = string.getBytes("UTF-8");
return encoder.encodeToString(textByte);
}

/**
* base64解码
* @param string
* @return
* @throws UnsupportedEncodingException
*/
public static String Base64Decoder(String string) throws UnsupportedEncodingException {
Decoder decoder = Base64.getDecoder();
return new String(decoder.decode(string), "UTF-8");
}
}


package com.crm.util;


import java.util.HashMap;
import java.util.Map;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.alibaba.fastjson.TypeReference;



public class JsonUtil {
/**
* json string 转换为 map 对象
*
* @param jsonObj
* @return
*/
public static Map jsonToMap(JSONObject jsonObj) {
return JSONObject.parseObject(jsonObj.toJSONString(), new TypeReference>(){});
}



/**
* 删除权限
* 2021/12/22优化 修复json文本中status可能不为字符串 优化流程
* */
public static Object RemoveJsonStatus(Object obj,String index,String status,String val,Object former) {
JSONArray jsonArray_=new JSONArray();
JSONObject jsonObjcet_=new JSONObject();
if( obj instanceof JSONArray ){
jsonArray_=(JSONArray)obj;
for(int i=0;i Object obj_1=jsonArray_.get(i);
former=RemoveJsonStatus(obj_1,index+"["+i+"]",status,val,former);
}
}else if( obj instanceof JSONObject ){
jsonObjcet_=(JSONObject)obj;
//System.out.println(jsonObjcet_.get("title")+"|"+index+"|child:"+jsonObjcet_.get("child"));
Object child=jsonObjcet_.get("child");
String statusv=(String) jsonObjcet_.get(status);
if(child!=null) {
if(statusv.equals(val)) {
System.out.println("删除权限:"+jsonObjcet_.get("title"));
JSONPath.remove(former, index);
}else {
former=RemoveJsonStatus(child,index+".child",status,val,former);
}
}else {
if(statusv.equals(val)) {
System.out.println("删除权限:"+jsonObjcet_.get("title"));
JSONPath.remove(former, index);
}
}

}
return former;
}

public static JSONArray JsonToMenuMap(Object obj,String index,Object former,int b,int isMenu) {
JSONArray jsonArray_=new JSONArray();
JSONObject jsonObjcet_=new JSONObject();
if( obj instanceof JSONArray ){
jsonArray_=(JSONArray)obj;
b=((JSONArray)former).size();
for(int i=0;i Object obj_1=jsonArray_.get(i);
if(jsonArray_.size()==4){isMenu=1;}
former=JsonToMenuMap(obj_1,index+"["+i+"]",former,b,isMenu);
}
}else if( obj instanceof JSONObject ){
jsonObjcet_=(JSONObject)obj;
Map tempMap = new HashMap();
JSONArray temp=(JSONArray)former;
if(jsonObjcet_.get("child")==null) {
isMenu=2;
}
if(b==0) {b=-1;}
tempMap.put("parentId", b);
tempMap.put("authorityId",temp.size()+1);
tempMap.put("index",index);
tempMap.put("title",jsonObjcet_.getString("title"));
tempMap.put("href",jsonObjcet_.getString("href"));
tempMap.put("icon",jsonObjcet_.getString("icon"));
tempMap.put("target",jsonObjcet_.getString("target"));
tempMap.put("status",jsonObjcet_.getString("status"));
tempMap.put("isMenu",isMenu);
String[] herfArr=jsonObjcet_.getString("href").split("\\/");
String permissionId="";
for (int i = 0; i < herfArr.length; ++i){
if(!herfArr[i].equals("..") && !herfArr[i].equals(".")) {
if(i!=herfArr.length-1) {
permissionId=permissionId+herfArr[i]+":";
}else {
permissionId=permissionId+herfArr[i];
}
}
}
tempMap.put("permissionId",permissionId);
temp.add(tempMap);
former=temp;
for (Object key : jsonObjcet_.keySet()) {
String keyStr = (String)key;
Object keyvalue = jsonObjcet_.get(keyStr);
if(index!=null && !index.equals("")) {
former=JsonToMenuMap(keyvalue,index+"."+keyStr,former,b,2);
}else {
former=JsonToMenuMap(keyvalue,keyStr,former,b,2);
}
}
}
return (JSONArray) former;
}

/**
* 根据href检测权限 多href用,分割
* */
public static Boolean JsonPermissionCheck(Object obj,String href,Boolean res) {
JSONArray jsonArray_=new JSONArray();
JSONObject jsonObjcet_=new JSONObject();
if(res!=null && res==true) {return true;}
if( obj instanceof JSONArray ){
jsonArray_=(JSONArray)obj;
for(int i=0;i Object obj_1=jsonArray_.get(i);
res=JsonPermissionCheck(obj_1,href,res);
if(res!=null && res==true) {return true;}
}
}else if( obj instanceof JSONObject ){
jsonObjcet_=(JSONObject)obj;
Object child=jsonObjcet_.get("child");
String statusv=(String) jsonObjcet_.get("status");
if(child!=null) {
String hrefi=jsonObjcet_.get("href").toString().replace("..","");
String hrefArr[]=href.split(",");
if(FindArray(hrefArr,hrefi)){
if(statusv.equals("1")) {
System.out.println("有权限:"+hrefi+"-"+href);
return true;
}else {
System.out.println("无权限:"+hrefi+"-"+href);
return false;
}
}
res=JsonPermissionCheck(child,href,res);
if(res!=null && res==true) {return true;}
}else {
String hrefi=jsonObjcet_.get("href").toString().replace("..","");
String hrefArr[]=href.split(",");
if(FindArray(hrefArr,hrefi)){
if(statusv.equals("1")) {
System.out.println("有权限:"+hrefi+"-"+href);
return true;
}else {
System.out.println("无权限:"+hrefi+"-"+href);
return false;
}
}
}
}
return res;
}

public static Boolean FindArray(String Arr[],String str) {
for(String i:Arr) {
if(i.equals(str)) {
return true;
}
}
return false;

}

}


7、项目运行截图

    (一)登录页面

(二)登录成功

(三)系统主界面 

(四)网站添加

 (五)权限列表

 (六)项目收款(分步)(七)系统操作日志

 (八)公告发布

(九)公告列表

 (十)菜单列表

 (十一)客户导入(Excel表)

(Excel表)

 (点击导入后)

 (十二)项目合同

 (十三)项目收款

 (十四)添加管理员

 (十五)添加客户

 (十六)退出

愿你有好运气,如果没有,愿你在不幸中学会慈悲;愿你被很多人爱,如果没有,愿你在寂寞中学会宽容!

  • 没有任何评论
个评论
mwh

mwh (青铜)

225金币 (0)粉丝 (2)源码

qq:3191707477

 

加入微信群,不定期分享源码和经验
签到活跃榜 连续签到送额外金币
最新博客
校园跑腿系统外卖系统软件平台大学生创业平台搭建 419
壹脉销客智能名片CRM系统小程序可二开源码交付部署 409
为啥没搞了 606
Nginx 的 5 大应用场景,太实用了! 881
CentOS 8-stream 安装Postgresql 详细教程 1035
JAVA智慧校园管理系统小程序源码 电子班牌 Sass 模式 1021
Java智慧校园系统源码 智慧校园源码 智慧学校源码 智慧校园管理系统源码 小程序+电子班牌 767
Java智慧校园系统源码 智慧校园源码 智慧学校源码 智慧校园管理系统源码 小程序+电子班牌 752
致远OA权限 1211
发博客会有金币吗 783