【Springboot】信息管理逻辑分析

(1)项目简介

(1.1)功能介绍

  1. 对分类的产品进行增删改查
  2. 分页查询
  3. 上传图片
  4. Redis缓存

(1.2)版本介绍

  1. Springboot:1.5.9.RELEASE
  2. JDK:1.8.0_201
  3. Maven:
  4. Mysql:5.5.15
  5. Redis

(1.3)结构介绍

(2)第一步:准备工作

(2.1)准备静态资源(css、图片、js)

  1. 图片资源:在img目录下的site目录放着所有要用到的静态图片
  2. css:
    back:这个目录里有一个style.css,这是后台界面用到的样式
    bootstrap:这里存放的是bootstrap的css样式文件
    fore:这个目录里也有一个style.css,这是前台界面用到的样式
  3. js:
    /bootstrap:bootstrap用到的js文件
    /jquery: jquery用到的js文件
    /axios:ajax 库
    /moment:日期格式化库
    /vue :vue.js 库
    在这里插入图片描述

(2.2)准备配置文件application.properties

#database
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tmall_springboot?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#表结构自动生成策略(none)
spring.jpa.hibernate.ddl-auto = none

#thymeleaf,使用 thymeleaf 作为视图
#LEGACYHTML5允许非严格的html出现,元素少点什么也可以编译通过
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
#cache=false 表示不要缓存,以免在开发过程中因为停留在缓存而给开发人员带来困扰
spring.thymeleaf.cache=false

#context
#上下文地址为 tmall_springboot, 所以访问的时候,都要加上这个
#比如:http://127.0.0.1:8080/tmall_springboot/admin
server.context-path=/tmall_springboot

#设置上传文件大小,默认只有1m
spring.http.multipart.maxFileSize=100Mb
spring.http.multipart.maxRequestSize=100Mb

#jpa对实体类的默认字段会把驼峰命名的属性
#转换为字段名的时候自动加上下划线。 这个配置的作用就是去掉下划线
#比如属性名称是 createDate, jpa 默认转换为字段名 create_Date
#有了这个配置之后,就会转换为同名字段 createDate
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

#显示 hibernate 执行的sql语句
#这个在上线之后,应该是关掉的,因为大量的 控制台输出会严重影响系统性能
#但是呢,因为本项目会和 redis 和 es 整合,打印 sql 语句的目的是为了观察 缓存是否起效果
spring.jpa.show-sql=true

(2.3)准备日志文件log4j.properties

log4j.rootLogger=debug, stdout, R

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

# Pattern to output the caller's file name and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=example.log

log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=5

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n

(3)第二步:包含的共通HTML

在这里插入图片描述

(3.1)导航栏

在这里插入图片描述

<div class="navitagorDiv" th:fragment="html" xmlns:th="http://www.w3.org/1999/xhtml">
	<nav class="navbar navbar-default navbar-fixed-top navbar-inverse">
		<img style="margin-left:10px;margin-right:0px" class="pull-left" src="img/site/tmallbuy.png" height="45px">
		<a class="navbar-brand" href="#nowhere">天猫后台</a>
		
		<a class="navbar-brand" href="admin_category_list">分类管理</a>
		<a class="navbar-brand" href="admin_user_list">用户管理</a>
		<a class="navbar-brand" href="admin_order_list">订单管理</a>
	</nav>
</div>

(3.2)页面共通的js函数

  1. checkEmpty:判断值是否为空
  2. getUrlParms:获取地址栏参数的函数
  3. checkNumber:判断是否是数字
  4. checkInt:判断是否整数
  5. checkDeleteLink:确实是否要删除
  6. jump:分页跳转函数,向前跳或者向后跳,或者跳转到第一页或者最后一页
  7. jumpByNumber:分页跳转函数,跳转到指定页
<template th:fragment="html(title)" xmlns:th="http://www.w3.org/1999/xhtml">
	<!--2-用到的一系列的 js 和 css 文件-->
	<script src="js/jquery/2.0.0/jquery.min.js"></script>
	<link href="css/bootstrap/3.3.6/bootstrap.min.css" rel="stylesheet">
	<script src="js/bootstrap/3.3.6/bootstrap.min.js"></script>
	<script src="js/vue/2.5.16/vue.min.js"></script>
	<script src="js/axios/0.17.1/axios.min.js"></script>
	<script src="js/moment/2.22.2/moment.js"></script> <!-- vue.js 格式化日期用的 -->
	<link href="css/back/style.css" rel="stylesheet">
	<!--每个后台页面都在一开始使用了adminHeader.html-->
	
	<!--3-各种自定义函数,这些函数都会在后台管理页面上用到-->
	<script>
	//判断是否为空
	function checkEmpty(value,text){
		if(null==value || value.length==0){
			alert(text+ "不能为空");
			return false;
		}
		return true;
	}	
	
    //获取地址栏参数的函数
    function getUrlParms(para){
	    var search=location.search; //页面URL的查询部分字符串
	    var arrPara=new Array(); //参数数组。数组单项为包含参数名和参数值的字符串,如“para=value”
	    var arrVal=new Array(); //参数值数组。用于存储查找到的参数值
	 
	    if(search!=""){	
	        var index=0;
	        search=search.substr(1); //去除开头的“?”
	        arrPara=search.split("&");
	 
	        for(i in arrPara){
	            var paraPre=para+"="; //参数前缀。即参数名+“=”,如“para=”
	            if(arrPara[i].indexOf(paraPre)==0&& paraPre.length<arrPara[i].length){
	                arrVal[index]=decodeURI(arrPara[i].substr(paraPre.length)); //顺带URI解码避免出现乱码
	                index++;
	            }
	        }
	    }
	 
	    if(arrVal.length==1){
	        return arrVal[0];
	    }else if(arrVal.length==0){
	        return null;
	    }else{
	        return arrVal;
	    }
    }	
    
    //判断是否数字 (小数和整数)
	function checkNumber(value, text){
		
		if(value.length==0){
			alert(text+ "不能为空");
			return false;
		}
		if(isNaN(value)){
			alert(text+ "必须是数字");
			return false;
		}
		return true;
	}
    //判断是否整数
	function checkInt(value, text){
		
		if(value.length==0){
			alert(text+ "不能为空");
			return false;
		}
		if(parseInt(value)!=value){
			alert(text+ "必须是整数");
			return false;
		}
		return true;
	}
    //确实是否要删除
	function checkDeleteLink(){
		var confirmDelete = confirm("确认要删除");
		if(confirmDelete)
			return true;
		return false;		
	}
    //分页跳转函数,向前跳或者向后跳,或者跳转到第一页或者最后一页
    function jump(page,vue){
		if('first'== page && !vue.pagination.first)
			vue.list(0);
		
		else if('pre'== page &&	vue.pagination.hasPrevious )
			vue.list(vue.pagination.number-1);
		
		else if('next'== page && vue.pagination.hasNext)
			vue.list(vue.pagination.number+1);					
		
		else if('last'== page && !vue.pagination.last)
			vue.list(vue.pagination.totalPages-1);    	
    }
    //分页跳转函数,跳转到指定页
    function jumpByNumber(start,vue){
    	if(start!=vue.pagination.number)
			vue.list(start);       	
    }
	</script>	
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<!--把传递进来的 title 参数显示在 title 元素里-->
	<title th:text="${title}" ></title>
</template>

(4)第三步:业务处理

(4.1)Redis配置类

//配置 Redis, 这个配置的作用主要是使得保存在 redis 里的key和value转换为如图所示的具有可读性的字符串,否则会是乱码,很不便于观察。
@Configuration
//Redis 缓存配置类
public class RedisConfig extends CachingConfigurerSupport {
 
    @Bean
    public CacheManager cacheManager(RedisTemplate<?,?> redisTemplate) {
        RedisSerializer stringSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.PUBLIC_ONLY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);  
         
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);         
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        CacheManager cacheManager = new RedisCacheManager(redisTemplate);
        return cacheManager;
  
    }
}

(4.2)实体类

  1. @Entity:表示这是一个实体类
  2. @Table(name = “category”):表示对应的表名是 category
@Entity//表示这是一个实体类
@Table(name = "category")//表示对应的表名是 category
//因为做前后端分离,前后端数据交互用的是json格式,那么Category对象就会被转换为json数据
//本项目使用jps来做实体咧的持久化,就会创造代理类来继承 Category ,并添加 handler 和 hibernateLazyInitializer 这两个无须 json 化的属性,所以这里需要用 JsonIgnoreProperties 把这两个属性忽略掉。
@JsonIgnoreProperties({ "handler","hibernateLazyInitializer" })
// @Data
public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Column(name = "id")//属性property:id对应上数据库列column:id
    int id;
    
    String name;
    
	public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

(4.3)持久层Dao(JPA)

使用JPA

// CategoryDAO 类集成了 JpaRepository,就提供了CRUD和分页 的各种常见功能。 这就是采用 JPA 方便的地方~
public interface CategoryDAO extends JpaRepository<Category,Integer>{

}

(4.4)业务层Service(Redis)

@Service//标记这个类是 Service类
@CacheConfig(cacheNames="categories")
public class CategoryService {
	@Autowired CategoryDAO categoryDAO;

	/**查询所有数据,并且实现分页
	 */
	@Cacheable(key="'categories-page-'+#p0+ '-' + #p1")
	public Page4Navigator<Category> list(int start, int size, int navigatePages) {
		//首先创建一个Sort对象,表示通过id倒排序
		Sort sort = new Sort(Sort.Direction.DESC, "id");
		Pageable pageable = new PageRequest(start, size,sort);
		//通过categoryDAO进行查询
		Page pageFromJPA =categoryDAO.findAll(pageable);
		//这里抛弃了 CategoryService 接口 加上 CategoryService 实现类的这种累赘的写法,而是直接使用 CategoryService 作为实现类来做。
		return new Page4Navigator<>(pageFromJPA,navigatePages);
	}
	
	/**查询所有数据
	 */
	@Cacheable(key="'categories-all'")
	public List<Category> list() {
    	Sort sort = new Sort(Sort.Direction.DESC, "id");
		return categoryDAO.findAll(sort);
	}

	/**添加数据
	 */
	@CacheEvict(allEntries=true)
	public void add(Category bean) {
		categoryDAO.save(bean);
	}

	/**删除数据
	 */
	@CacheEvict(allEntries=true)
	public void delete(int id) {
		categoryDAO.delete(id);
	}

	/**根据id查询单个数据
	 */
	@Cacheable(key="'categories-one-'+ #p0")
	public Category get(int id) {
		Category c= categoryDAO.findOne(id);
		return c;
	}

	/**更新数据
	 */
	@CacheEvict(allEntries=true)
	public void update(Category bean) {
		categoryDAO.save(bean);
	}
}

(4.5)后台管理页面跳转专用控制器AdminPageController

后台管理页面跳转专用控制器
因为是做前后端分离,所以数据是通过 RESTFUL接口来取的
而在业务上,除了 RESTFUL 服务要提供,还要提供页面跳转服务
所以所有的后台页面跳转都放在 AdminPageController 这个控制器里
而RSTFUL 专门放在 Category 对应的控制器 CategoryController.java 里面。

@Controller
public class AdminPageController {
	//路径为admin,客户端跳转到admin_category_list路径
	@GetMapping(value="/admin")
    public String admin(){
		return "redirect:admin_category_list";
    }

    //路径为admin_category_list,返回到admin文件夹下的listCategory.html
	@GetMapping(value="/admin_category_list")
	public String listCategory(){
		return "admin/listCategory";
	}

	//路径为admin_category_edit,返回到admin文件夹下的editCategory.html
	@GetMapping(value="/admin_category_edit")
	public String editCategory(){
		return "admin/editCategory";
	}
}

(4.6)专门用来提供RESTFUL服务器控制器CategoryController

对每个方法的返回值都会直接转换为 json 数据格式

@RestController
public class CategoryController {
   @Autowired
   CategoryService categoryService;

   //对于categories 访问,会获取所有的 Category对象集合,并返回这个集合
   //因为是声明为 @RestController, 所以这个集合,又会被自动转换为 JSON数组抛给浏览器。
   @GetMapping("/categories")
   //修改原 list 方法,接受 start 和 size 参数
   public Page4Navigator<Category> list(@RequestParam(value = "start", defaultValue = "0") int start, @RequestParam(value = "size", defaultValue = "5") int size) throws Exception {
       start = start<0?0:start;
       //返回的是 Page4Navigator 类型,并通过 RestController 转换为 json 对象抛给浏览器
       Page4Navigator<Category> page =categoryService.list(start, size, 5);  //5表示导航分页最多有5个,像 [1,2,3,4,5] 这样
       return page;
   }

   @PostMapping("/categories")
   public Object add(Category bean, MultipartFile image, HttpServletRequest request) throws Exception {
       categoryService.add(bean);
       saveOrUpdateImageFile(bean, image, request);
       return bean;
   }

   public void saveOrUpdateImageFile(Category bean, MultipartFile image, HttpServletRequest request)
           throws IOException {
       File imageFolder= new File(request.getServletContext().getRealPath("img/category"));
       File file = new File(imageFolder,bean.getId()+".jpg");
       if(!file.getParentFile().exists())
           file.getParentFile().mkdirs();
       image.transferTo(file);
       BufferedImage img = ImageUtil.change2jpg(file);
       ImageIO.write(img, "jpg", file);
   }

   @DeleteMapping("/categories/{id}")
   public String delete(@PathVariable("id") int id, HttpServletRequest request)  throws Exception {
       categoryService.delete(id);
       File  imageFolder= new File(request.getServletContext().getRealPath("img/category"));
       File file = new File(imageFolder,id+".jpg");
       file.delete();
       return null;
   }

   @GetMapping("/categories/{id}")
   public Category get(@PathVariable("id") int id) throws Exception {
       Category bean=categoryService.get(id);
       return bean;
   }

   @PutMapping("/categories/{id}")
   public Object update(Category bean, MultipartFile image,HttpServletRequest request) throws Exception {
       String name = request.getParameter("name");
       bean.setName(name);
       categoryService.update(bean);

       if(image!=null) {
           saveOrUpdateImageFile(bean, image, request);
       }
       return bean;
   }

}

(4.7)启动类Application

@SpringBootApplication
@EnableCaching//Redis使用增加注解,用来启动缓存
public class Application {
    //检查端口6379是否启动,就是Redis服务器使用的端口,如果没有启动就会退出springboot
    static {
        PortUtil.checkPort(6379,"Redis 服务端",true);
    }
    public static void main(String[] args) {
    	SpringApplication.run(Application.class, args);
    }
}

(5)其他类

(5.1)配置类CORSConfiguration.java(用于允许所有的请求都跨域)

因为是二次请求,第一次是获取 html 页面, 第二次通过 html 页面上的 js 代码异步获取数据,一旦部署到服务器就容易面临跨域请求问题,所以允许所有访问都跨域,就不会出现通过 ajax 获取数据获取不到的问题了。

@Configuration
public class CORSConfiguration extends WebMvcConfigurerAdapter{
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		//所有请求都允许跨域
		registry.addMapping("/**")
				.allowedOrigins("*")
				.allowedMethods("*")
				.allowedHeaders("*");
	}
}

(5.2)异常处理类GloabalExceptionHandler.java

主要是在处理删除父类信息的时候,因为外键约束的存在,而导致违反约束。

@RestController
@ControllerAdvice
public class GloabalExceptionHandler {
    @ExceptionHandler(value = Exception.class)
    public String defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
    	e.printStackTrace();
    	Class constraintViolationException = Class.forName("org.hibernate.exception.ConstraintViolationException");
    	if(null!=e.getCause()  && constraintViolationException==e.getCause().getClass()) {
    		return "违反了约束,多半是外键约束";
    	}
        return e.getMessage();
    }
}

(5.3)端口监测工具类PortUtil

用于判断某个端口是否启动。 因为常常忘记启动 redis服务器,而导致系统无法运行, 这个工具的作用,是帮助检查是否启动了,在启动类中使用

public class ImageUtil {
  
    public static BufferedImage change2jpg(File f) {
        try {
            Image i = Toolkit.getDefaultToolkit().createImage(f.getAbsolutePath());
            PixelGrabber pg = new PixelGrabber(i, 0, 0, -1, -1, true);
            pg.grabPixels();
            int width = pg.getWidth(), height = pg.getHeight();
            final int[] RGB_MASKS = { 0xFF0000, 0xFF00, 0xFF };
            final ColorModel RGB_OPAQUE = new DirectColorModel(32, RGB_MASKS[0], RGB_MASKS[1], RGB_MASKS[2]);
            DataBuffer buffer = new DataBufferInt((int[]) pg.getPixels(), pg.getWidth() * pg.getHeight());
            WritableRaster raster = Raster.createPackedRaster(buffer, width, height, width, RGB_MASKS, null);
            BufferedImage img = new BufferedImage(RGB_OPAQUE, raster, false, null);
            return img;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return null;
        }
    }
  
    public static void resizeImage(File srcFile, int width,int height, File destFile) {
        try {
            if(!destFile.getParentFile().exists())
                destFile.getParentFile().mkdirs();
            Image i = ImageIO.read(srcFile);
            i = resizeImage(i, width, height);
            ImageIO.write((RenderedImage) i, "jpg", destFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
      
    public static Image resizeImage(Image srcImage, int width, int height) {
        try {
            BufferedImage buffImg = null;
            buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            buffImg.getGraphics().drawImage(srcImage.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);
  
            return buffImg;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

(6)前端画面

(6.1)显示画面listCategory.html

<!--分类查询对应的html文件-->
<!--一共用到了4个公共包含文件-->
<!--1-分类管理-->
<head th:include="include/admin/adminHeader::html('分类管理')" ></head>
<body>
<!--2-导航栏-->
<div th:replace="include/admin/adminNavigator::html" ></div>
<script>

    <!--获取数据-->
    $(function(){
        //这个是jquery的代码,表示当整个html加载好了之后执行
        var data4Vue = {
            //vue用到的数据, uri表示访问哪个地址去获取数据,这里的值是categories
            //和CategoryController.java 相呼应
            uri:'categories',
            beans: [],
            bean: { id: 0, name: ''},
            pagination:{},
            file: null
        };

        //ViewModel
        //创建Vue对象
        //一个Vue对象有4部分,el是关联视图,data是传递的参数,mounted是vue加载成功后的执行,moethods是方法群
        var vue = new Vue({
            //el表示和本页面的 <div id="workingArea" > 元素绑定
            el: '#workingArea',
            //data表示vue 使用上面的data4Vue对象。
            data: data4Vue,
            //加载Vue对象成功之后会调用,成功的时候去调用 list() 函数
            mounted:function(){ //mounted表示这个 Vue 对象加载成功了
                this.list(0);
            },
            methods: {
                //list 函数使用 data4Vue里的 uri作为地址,然后调用 axios.js 这个 ajax库,进行异步调用
                list:function(start){
                    var url =  this.uri+ "?start="+start;
                    axios.get(url).then(function(response) {
                        vue.pagination = response.data;
                        //调用成功之后,把服务端返回的数据,保存在 vue.beans 上
                        vue.beans = response.data.content;
                    });
                },
                add: function () {
                    if(!checkEmpty(this.bean.name, "分类名称"))
                        return;
                    if(!checkEmpty(this.file, "分类图片"))
                        return;
                    var url = this.uri;

                    //axios.js 上传文件要用 formData 这种方式
                    var formData = new FormData();
                    formData.append("image", this.file);
                    formData.append("name", this.bean.name);
                    axios.post(url,formData).then(function(response){
                        vue.list(0);
                        vue.bean = { id: 0, name: '', hp: '0'};
                        $("#categoryPic").val('');
                        vue.file = null;
                    });
                },
                deleteBean: function (id) {
                    if(!checkDeleteLink())
                        return;
                    var url = this.uri+"/"+id;
                    axios.delete(url).then(function(response){
                        if(0!=response.data.length){
                            alert(response.data);
                        }
                        else{
                            vue.list(0);
                        }
                    });
                },
                getFile: function (event) {
                    this.file = event.target.files[0];
                },
                //增加了两个跳转方法,分别是 jump和 jumpByNumber,而这两个方法的定义在 前面讲解的 adminHeader.html 里
                jump: function(page){
                    jump(page,vue); //定义在adminHeader.html 中
                },
                jumpByNumber: function(start){
                    jumpByNumber(start,vue);
                }
            }
        });
    });

//上面是获取数据,这里就是显示数据
</script>
<div id="workingArea" >
    <h1 class="label label-info" >分类管理</h1>
    <br>
    <br>
    <div class="listDataTableDiv">
        <table class="table table-striped table-bordered table-hover  table-condensed">
            <thead>
            <tr class="success">
                <th>ID</th>
                <th>图片</th>
                <th>分类名称</th>
                <th>属性管理</th>
                <th>产品管理</th>
                <th>编辑</th>
                <th>删除</th>
            </tr>
            </thead>
            <tbody>
            <!--使用 v-for进行遍历, 这个 beans 就表示data4Vue里面的beans属性-->
            <tr v-for="bean in beans ">
                <!--bean就是遍历出来的每个id, 这里就是输出每个分类的id.-->
                <td>{{bean.id}}</td>
                <td>
                    <img height="40px"  :src="'img/category/'+bean.id+'.jpg'">
                </td>
                <td>
                    {{bean.name}}
                </td>
                <td>
                    <!--在超链里的href里拼接分类id.-->
                    <a :href="'admin_property_list?cid=' + bean.id "><span class="glyphicon glyphicon-th-list"></span></a>
                </td>
                <td>
                    <a :href="'admin_product_list?cid=' + bean.id "><span class="glyphicon glyphicon-shopping-cart"></span></a>
                </td>
                <td>
                    <a :href="'admin_category_edit?id=' + bean.id "><span class="glyphicon glyphicon-edit"></span></a>
                </td>
                <td>
                    <a href="#nowhere"  @click="deleteBean(bean.id)"><span class="glyphicon glyphicon-trash"></span></a>
                </td>
            </tr>
            </tbody>
        </table>
    </div>
    <!--3-分页页面-->
    <div th:replace="include/admin/adminPage::html" ></div>
    <div class="panel panel-warning addDiv">
        <div class="panel-heading">新增分类</div>
        <div class="panel-body">
            <table class="addTable">
                <tr>
                    <td>分类名称</td>
                    <td><input  @keyup.enter="add" v-model.trim="bean.name" type="text" class="form-control"></td>
                </tr>
                <tr>
                    <td>分类图片</td>
                    <td>
                        <input id="categoryPic" accept="image/*" type="file" name="image" @change="getFile($event)" />
                    </td>
                </tr>
                <tr class="submitTR">
                    <td colspan="2" align="center">
                        <a href="#nowhere"  @click="add" class="btn btn-success">提交</a>
                    </td>
                </tr>
            </table>
        </div>
    </div>
</div>
<!--4-底部显示-->
<div th:replace="include/admin/adminFooter::html" ></div>
</body>
</html>

(6.2)编辑画面editCategory.html

<!--一个项目里面有很多可以共用的部分,使用包含技术可以把这些共用部分包含起来-->
<head th:include="include/admin/adminHeader::html('编辑分类')" ></head>
<body>
<div th:replace="include/admin/adminNavigator::html" ></div>
<script>
    $(function(){
        var data4Vue = {
            uri: 'categories',
            listURL:'admin_category_list',
            bean: { id: 0, name: '', hp: '0'},
            file:''
        };

        //ViewModel
        var vue = new Vue({
            el: '#workingArea',
            data: data4Vue,
            mounted:function(){ //mounted 表示这个 Vue 对象加载成功了
                this.get();
            },
            methods: {
                get:function(){
                    var id = getUrlParms("id");
                    var url = this.uri+"/"+id;
                    axios.get(url).then(function(response) {
                        vue.bean = response.data;
                    })
                },
                update:function () {
                    if(!checkEmpty(this.bean.name, "分类名称"))
                        return;
                    var url = this.uri+"/"+this.bean.id;

                    //axios.js 上传文件要用 formData 这种方式
                    var formData = new FormData();
                    formData.append("image", this.file);
                    formData.append("name", this.bean.name);
                    axios.put(url,formData).then(function(response){
                        location.href=vue.listURL;
                    });
                },
                getFile: function (event) {
                    this.file = event.target.files[0];
                }
            }
        });
    });
</script>

<div id="workingArea">

    <ol class="breadcrumb">
        <li><a href="admin_category_list">所有分类</a></li>
        <li class="active">编辑分类</li>
    </ol>

    <div class="panel panel-warning editDiv">
        <div class="panel-heading">编辑分类</div>
        <div class="panel-body">
            <table class="editTable">
                <tr>
                    <td>分类名称</td>
                    <td><input  @keyup.enter="update" v-model.trim="bean.name" type="text" class="form-control"></td>
                </tr>
                <tr>
                    <td>分类图片</td>
                    <td>
                        <input id="categoryPic" accept="image/*" type="file" name="image" @change="getFile($event)" />
                    </td>
                </tr>
                <tr class="submitTR">
                    <td colspan="2" align="center">
                        <input type="hidden" name="id"   v-model.trim="bean.id" >
                        <a href="#nowhere" class="btn btn-success" @click="update">提 交</a>
                    </td>
                </tr>
            </table>
        </div>
    </div>
</div>

<div th:replace="include/admin/adminFooter::html" ></div>
</body>

(7)分页

(8)业务流程

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值