整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

终于搞懂如何用Java去除HTML标签了

我平时的工作中,偶尔会用 Java 做一些解析HTML的工作。有的时候我需要删除所有的HTML标签,只保留纯文字内容。这个问题在做过一些爬虫工作的朋友来说很简单。下面来说说,我们平时使用到的集中解析的方法。

使用正则表达式

通过爬虫爬到的HTML内容,从程序角度来讲,就是一个字符串。我们可以对其按照纯文本处理的方式来处理。

我们在做文本处理的时候,第一个想到的就是正则表达式。从一个字符串中删除HTML,对于正则来说,还是比较简单的。毕竟还是有固定的格式,比如“<...>”。

我们常用的的正则就是 <[^>]> 或者 <.*?>

我们在使用正则的时候,需要注意的是正则默认是贪婪匹配。也就是说,正则表达式<.*> 能够匹配到更多的HTML内容,而不是单个标签。

现在,让我们测试一下它是否能从HTML源中删除标签。

正则测试删除标签1

在我们测试删除HTML标签之前,首先让我们创建一个HTML例子,例如example1.html

<!DOCTYPE html>
<html>
<head>
    <title>这是标题</title>
</head>
<body>
    <p>
        如果应用程序X没有启动,可能的原因是<br/>
        1. <a href="https://maven.apache.org">Maven</a>没有安装<br/>
        2. 磁盘空间不足<br/>
        3. 内存不足
    </p>
</body>
</html>

现在,让我们写一个测试,用String.replaceAll()来删除HTML标签。

String html = ... // load example1.html
String result = html.replaceAll("<[^>]`>", "");
System.out.println(result);

如果我们运行这个测试方法,我们会看到结果。

    这是标题



        如果应用程序X没有启动,可能的原因是
        1.Maven没有安装
        2.磁盘空间不足
        3.没有足够的内存

输出结果保留了剥离后的HTML的空白处。我们在处理提取的文本时,可以很容易地删除或跳过这些空行或空白处。

正则测试删除标签2

我们刚才已经看到了,通过使用Regex来删除HTML标签是非常简单。但是粗暴的使用这种方法会有很多问题,我们不能预测最终的结果会是怎么样的。

例如,一个HTML文档可能有<script><style>标签,而我们可能不希望在结果中出现它们的内容。

此外,<script><style>、甚至是<body>标签中的文本可能包含 <>字符。如果是这种情况,我们的正则方法可能会出错。

现在,让我们看看另一个例子,比如example2.html

<!DOCTYPE HTML>
<html>
<head>
    <title>这是标题</title>
</head>
<script>
    // some js function
</script>
<body>
    <p>
        如果应用程序X没有启动,可能的原因是<br/>
        1. <a
            id="link"
            href="http://maven.apache.org/">
            Maven
            </a> 没有安装<br/>
        2. 磁盘空间不足 (<1G) <br/>
        3. 内存不足(<64MB)<br/>
    </p>
</body>
</html>

现在我们有一个<script>标签和 <字符在<body>标签内。

如果我们对example2.html使用同样的方法,我们会得到如下内容。

   这是标题
    // some js function
        如果应用程序X没有启动,可能的原因是
        1. 
            Maven
             没有安装
        2. 磁盘空间不足 (
        3. 内存不足(

显然,由于"<"字符的存在,我们丢失了一些文本。所以正则在处理文本的时候并不是万能的。我们可以使用一些 HTML 解析器来做这些比较复杂的场景。

使用Jsoup

Jsoup 是一个流行的HTML解析库,如果想要从一个HTML文档中提取文本,我们可以简单地调用Jsoup.parse(htmlString).text()

在项目中使用的时候,我们首先需要添加 jsoup 的依赖库,我们这里就通过maven的方式引入。

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.14.3</version>
</dependency>

我们用 example2.html来测试一下。

String html = ... // load example2.html
System.out.println(Jsoup.parse(html).text());

如果我们让这个方法运行,它就会打印出来。

这是标题 如果应用程序X没有启动,可能的原因是 1.Maven没有安装 2.没有足够的(<1G)磁盘空间 3.没有足够的(<64MB)内存

从输出结果可知,Jsoup已经成功地从HTML文档中提取了文本。另外,<script>元素中的文本已经被忽略了。

此外,默认情况下,Jsoup会删除所有的文本格式和空白处,比如换行符。

使用HTMLCleaner

HTMLCleaner 也是一个HTML解析库。

首先,我们需要在pom.xml中添加HTMLCleaner 依赖。

<dependency>
    <groupId>net.sourceforge.htmlcleaner</groupId>
    <artifactId>htmlcleaner</artifactId>
    <version>2.25</version>
</dependency>

我们可以设置[各种参数](http://htmlcleaner.sourceforge.net/parameters.php)来控制HTMLCleaner的解析行为。我们在这里使用HTMLCleaner在解析example2.html时跳过<script>元素。

String html = ... // load example2.html
CleanerProperties props = new CleanerProperties();
props.setPruneTags("script");
String result = new HtmlCleaner(props).clean(html).getText().toString();
System.out.println(result);

运行一下,HTMLCleaner将产生这样的输出。

这是标题



        如果应用程序X没有启动,可能的原因是:
        1.Maven没有安装
        2.没有足够的(<1G)磁盘空间
        3.内存不足(<64MB)

我们可以看到,<script>元素中的内容被忽略了, <br/>标签转换为提取的文本中的换行符。另外, HTMLCleaner 保留了HTML的空白内容。

总结

在这篇文章中,我们学习了几种去除HTML的方法,我们需要注意的是,正则在文本处理的过程中并不是万能的。

程目标

目标1:完成商家后台商品列表的功能

目标2:完成商家后台商品修改的功能

目标3:完成运营商后台商品审核的功能

目标4:完成运营商后台商品删除的功能

目标5:掌握注解式事务的配置

1.商家后台-商品管理【商品列表】

1.1需求分析

在商家后台,显示该商家的商品列表信息,如下图:

1.2查询商家商品列表

1.2.1后端代码

修改pinyougou-shop-web工程的GoodsController.java的search方法

	@RequestMapping("/search")
	public PageResult search(@RequestBody TbGoods goods, int page, int rows ){
		//获取商家ID
		String sellerId = SecurityContextHolder.getContext().getAuthentication().getName();
		//添加查询条件 
		goods.setSellerId(sellerId);		
		return goodsService.findPage(goods, page, rows);		
	}

修改pinyougou-sellergoods-service 工程com.pinyougou.sellergoods.service.impl 的findPage方法,修改条件构建部分代码,将原来的模糊匹配修改为精确匹配

if(goods.getSellerId()!=null && goods.getSellerId().length()>0){
	//criteria.andSellerIdLike("%"+goods.getSellerId()+"%");
	criteria.andSellerIdEqualTo(goods.getSellerId());
}

1.2.2前端代码

修改goods.html. 引入js

<script type="text/javascript" src="../plugins/angularjs/angular.min.js"></script>
<!-- 分页组件开始 -->
<script src="../plugins/angularjs/pagination.js"></script>
<link rel="stylesheet" href="../plugins/angularjs/pagination.css">
<!-- 分页组件结束 -->
<script type="text/javascript" src="../js/base_pagination.js"></script>	
<script type="text/javascript" src="../js/service/goodsService.js"></script>
<script type="text/javascript" src="../js/service/itemCatService.js"></script>
<script type="text/javascript" src="../js/service/uploadService.js"></script>
<script type="text/javascript" src="../js/service/typeTemplateService.js"></script>
<script type="text/javascript" src="../js/controller/baseController.js"></script>
<script type="text/javascript" src="../js/controller/goodsController.js"></script>

添加指令

<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController">

在页面上放置分页控件

<tm-pagination conf="paginationConf"></tm-pagination>

循环列表

<tr ng-repeat="entity in list">
 <td><input type="checkbox"></td>			 
 	<td>{{entity.id}}</td>
 <td>{{entity.goodsName}}</td>
 <td>{{entity.price}}</td>
 <td>{{entity.category1Id}}</td>
 <td>{{entity.category2Id}}</td>
 <td>{{entity.category3Id}}</td>
 <td>
 	{{entity.auditStatus}}
 </td>		 
 <td class="text-center"> 
 	 <button type="button" class="btn bg-olive btn-xs">修改</button> 
 </td>
</tr>

显示效果如下:

1.3显示状态

修改goodsController.js,添加state数组

$scope.status=['未审核','已审核','审核未通过','关闭'];//商品状态

修改列表显示

{{status[entity.auditStatus]}}

显示效果如下:

1.4显示分类

我们现在的列表中的分类仍然显示ID

如何才能显示分类的名称呢?

方案一:在后端代码写关联查询语句,返回的数据中直接有分类名称。

方案二:在前端代码用ID去查询后端,异步返回商品分类名称。

我们目前采用方案二:

(1)修改goodsController

$scope.itemCatList=[];//商品分类列表
//加载商品分类列表
$scope.findItemCatList=function(){		
	itemCatService.findAll().success(
			function(response){							
				for(var i=0;i<response.length;i++){
					$scope.itemCatList[response[i].id]=response[i].name;
				}
			}
	);
}

代码解释:因为我们需要根据分类ID得到分类名称,所以我们将返回的分页结果以数组形式再次封装。

(2)修改goods.html ,增加初始化调用

<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="findItemCatList()">

(3)修改goods.html , 修改列表

<td>{{itemCatList[entity.category1Id]}}</td>
<td>{{itemCatList[entity.category2Id]}}</td>
<td>{{itemCatList[entity.category3Id]}}</td>

1.5条件查询

根据状态和商品名称进行查询

修改goods.html

<div class="has-feedback">
 	状态:<select ng-model="searchEntity.auditStatus">
 	 <option value="">全部</option> 
 <option value="0">未审核</option> 
 <option value="1">已审核</option> 
 <option value="2">审核未通过</option> 
 <option value="3">关闭</option> 
 </select>
		商品名称:<input ng-model="searchEntity.goodsName">						
		<button class="btn btn-default" ng-click="reloadList()">查询</button> 
</div>

2.商家后台-商品管理【商品修改】

2.1需求分析

在商品列表页面点击修改,进入商品编辑页面,并传递参数商品ID,商品编辑页面接受该参数后从数据库中读取商品信息,用户修改后保存信息。

2.2基本信息读取

我们首选读取商品分类、商品名称、品牌,副标题,价格等信息

2.2.1后端代码

(1)修改pinyougou-sellergoods-interface的GoodsService.java

	/**
	 * 根据ID获取实体
	 * @param id
	 * @return
	 */
	public Goods findOne(Long id);

(2)修改pinyougou-sellergoods-service的GoodsServiceImpl.java

	@Override
	public Goods findOne(Long id) {
		Goods goods=new Goods();
		TbGoods tbGoods = goodsMapper.selectByPrimaryKey(id);
		goods.setGoods(tbGoods);
		TbGoodsDesc tbGoodsDesc = goodsDescMapper.selectByPrimaryKey(id);
		goods.setGoodsDesc(tbGoodsDesc);
		return goods;
	}

(3)修改pinyougou-shop-web(和pinyougou-manager-web)的GoodsController.java

	/**
	 * 获取实体
	 * @param id
	 * @return
	 */
	@RequestMapping("/findOne")
	public Goods findOne(Long id){
		return goodsService.findOne(id);		
	}

2.2.2前端代码

(1)在goodsController中引入$location服务

//商品控制层(商家后台)
app.controller('goodsController',function($scope,$controller,$location,goodsService,uploadService,item_catService,type_templateService){
......

(2)修改goodsController 添加代码:

	//查询实体 
	$scope.findOne=function(){			
		var id= $location.search()['id'];//获取参数值
		if(id==null){
			return ;
		}
		goodsService.findOne(id).success(
			function(response){
				$scope.entity= response;					
			}
		);				
	}

在goods_edit.html页面上添加指令

<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="selectItemCat1List();findOne()">

测试:

地址栏输入

http://localhost:9102/admin/goods_edit.html#?id=149187842867969

注意: ?前要加# ,则是angularJS的地址路由的书写形式

2.3读取商品介绍(富文本编辑器)

修改前端代码 goodsController

//查询实体 
$scope.findOne=function(){			
		.................
		goodsService.findOne(id).success(
			function(response){
				$scope.entity= response;	
				//向富文本编辑器添加商品介绍
				editor.html($scope.entity.goodsDesc.introduction);
			}
		);				
}

2.4显示商品图片列表

修改goodsController.js ,在dataLogic方法添加代码,将图片列表由字符串转换为json集合对象

//查询实体 
$scope.findOne=function(){		
 ..............	
		//如果有ID,则查询实体
		goodsService.findOne(id).success(
			function(response){
				 $scope.entity= response;	
				 //向富文本编辑器添加商品介绍
				 editor.html($scope.entity.goodsDesc.introduction);
				 //显示图片列表
$scope.entity.goodsDesc.itemImages= 
JSON.parse($scope.entity.goodsDesc.itemImages);
			}
		);				
}

2.5读取商品扩展属性

修改goodsController.js

	//查询实体 
	$scope.findOne=function(){			
		.........
		goodsService.findOne(id).success(
			function(response){
				.......................
				//显示扩展属性
				$scope.entity.goodsDesc.customAttributeItems= JSON.parse($scope.entity.goodsDesc.customAttributeItems);	
			}
		);				
	}		

经过测试,我们发现扩展属性值并没有读取出来,这是因为与下列代码发生冲突

$scope.$watch('entity.goods.typeTemplateId',function(newValue,oldValue){
 ......
$scope.entity.goodsDesc.customAttributeItems = JSON.parse($scope.typeTemplate.customAttributeItems);//扩展属性
}

我们读取出来的值被覆盖了,我们需要改写代码, 添加判断,当用户没有传递id参数时再执行此逻辑

//监控模板ID ,读取品牌列表
$scope.$watch('entity.goods.typeTemplateId',function(newValue,oldValue){
		//读取品牌列表和扩展属性
		typeTemplateService.findOne(newValue).success(
			function(response){
				.......
				//如果没有ID,则加载模板中的扩展数据
				if($location.search()['id']==null){
					$scope.entity.goodsDesc.customAttributeItems = JSON.parse($scope.typeTemplate.customAttributeItems);//扩展属性	
				}				
			}
		);
		.......
});

2.6读取商品规格属性

修改goodsController

	//查询实体 
	$scope.findOne=function(){		
		......
		goodsService.findOne(id).success(
			function(response){
				$scope.entity= response;		
				editor.html($scope.entity.goodsDesc.introduction);//商品介绍
				$scope.entity.goodsDesc.itemImages= JSON.parse($scope.entity.goodsDesc.itemImages);//图片列表
				//扩展属性列表
				$scope.entity.goodsDesc.customAttributeItems =JSON.parse($scope.entity.goodsDesc.customAttributeItems);
				//规格				$scope.entity.goodsDesc.specificationItems=JSON.parse($scope.entity.goodsDesc.specificationItems);				
			}
		);				
	}
//根据规格名称和选项名称返回是否被勾选
$scope.checkAttributeValue=function(specName,optionName){
	var items= $scope.entity.goodsDesc.specificationItems;
	var object= $scope.searchObjectByKey(items,'attributeName',specName);
	if(object==null){
		return false;
	}else{
		if(object.attributeValue.indexOf(optionName)>=0){
			return true;
		}else{
			return false;
		}
	}			
}

修改页面上规格面板的复选框,运用 ng-checked指令控制复选框的勾选状态

<input type="checkbox" 		ng-click="updateSpecAttribute($event,pojo.text,p.optionName);createSKUTable()" 		ng-checked="checkAttributeValue(pojo.text,p.optionName)">{{p.optionName}}

2.7读取SKU数据

显示SKU商品列表,并自动读取价格、库存等数据加载到列表中

2.7.1后端代码

在GoodsServiceImpl的findOne方法中加载SKU商品数据

		//查询SKU商品列表
		TbItemExample example=new TbItemExample();
		com.pinyougou.pojo.TbItemExample.Criteria criteria = example.createCriteria();
		criteria.andGoodsIdEqualTo(id);//查询条件:商品ID
		List<TbItem> itemList = itemMapper.selectByExample(example);		
		goods.setItemList(itemList);

2.7.2前端代码

在goodsController.js修改findOne方法的代码

//查询实体 
$scope.findOne=function(){			
	........
	goodsService.findOne(id).success(
		function(response){
			$scope.entity= response;	
			.........			
			//SKU列表规格列转换				
			for( var i=0;i<$scope.entity.itemList.length;i++ ){
$scope.entity.itemList[i].spec = 
JSON.parse( $scope.entity.itemList[i].spec);		
			}			
		}
	);				
}

2.8保存数据

2.8.1后端代码

修改 pinyougou-sellergoods-interface 的 GoodsService.java

public void update(Goods goods);

修改pinyougou-sellergoods-service的GoodsServiceImpl ,将SKU列表插入的代码提取出来,封装到私有方法中

/**
 * 插入SKU列表数据
 * @param goods
 */
private void saveItemList(Goods goods){		
	if("1".equals(goods.getGoods().getIsEnableSpec())){
		for(TbItem item :goods.getItemList()){
			.........中间代码略
		}		
	}else{			
		TbItem item=new TbItem();
		.........中间代码略
		itemMapper.insert(item);
	}		
}

在add方法中调用 此方法,修改如下:

public void add(Goods goods) {
	goods.getGoods().setAuditStatus("0");		
	goodsMapper.insert(goods.getGoods());	//插入商品表
	goods.getGoodsDesc().setGoodsId(goods.getGoods().getId());
	goodsDescMapper.insert(goods.getGoodsDesc());//插入商品扩展数据
	saveItemList(goods);//插入商品SKU列表数据
}

怎么样,是不是比原来更加清爽了呢?

接下来,我们修改update方法,实现修改

public void update(Goods goods){
	goods.getGoods().setAuditStatus("0");//设置未申请状态:如果是经过修改的商品,需要重新设置状态
	goodsMapper.updateByPrimaryKey(goods.getGoods());//保存商品表
	goodsDescMapper.updateByPrimaryKey(goods.getGoodsDesc());//保存商品扩展表
	//删除原有的sku列表数据		
	TbItemExample example=new TbItemExample();
	com.pinyougou.pojo.TbItemExample.Criteria criteria = example.createCriteria();
	criteria.andGoodsIdEqualTo(goods.getGoods().getId());	
	itemMapper.deleteByExample(example);
	//添加新的sku列表数据
	saveItemList(goods);//插入商品SKU列表数据	
}	

修改pinyougou-manager-web工程的GoodsController.java

	@RequestMapping("/update")
	public Result update(@RequestBody Goods goods){
		......
	}	

修改pinyougou-shop-web工程的GoodsController.java

	/**
	 * 修改
	 * @param goods
	 * @return
	 */
	@RequestMapping("/update")
	public Result update(@RequestBody Goods goods){
		//校验是否是当前商家的id		
		Goods goods2 = goodsService.findOne(goods.getGoods().getId());
		//获取当前登录的商家ID
		String sellerId = SecurityContextHolder.getContext().getAuthentication().getName();
		//如果传递过来的商家ID并不是当前登录的用户的ID,则属于非法操作
		if(!goods2.getGoods().getSellerId().equals(sellerId) || !goods.getGoods().getSellerId().equals(sellerId) ){
			return new Result(false, "操作非法");		
		}		
		try {
			goodsService.update(goods);
			return new Result(true, "修改成功");
		} catch (Exception e) {
			e.printStackTrace();
			return new Result(false, "修改失败");
		}
	}

代码解释:出于安全考虑,在商户后台执行的商品修改,必须要校验提交的商品属于该商户

2.8.2前端代码

(1)修改goodsController.js ,新增保存的方法

//保存 
$scope.save=function(){			
	//提取文本编辑器的值
	$scope.entity.goodsDesc.introduction=editor.html();	
	var serviceObject;//服务层对象 				
	if($scope.entity.goods.id!=null){//如果有ID
		serviceObject=goodsService.update( $scope.entity ); //修改 
	}else{
		serviceObject=goodsService.add( $scope.entity );//增加 
	}				
	serviceObject.success(
		function(response){
			if(response.success){
				alert('保存成功');					
				$scope.entity={};
				editor.html("");
			}else{
				alert(response.message);
			}
		}		
	);				
}

(2)修改goods_edit.html 调用

<button class="btn btn-primary" ng-click="save()"><i class="fa fa-save"></i>保存</button>

2.9页面跳转

(1)由商品列表页跳转到商品编辑页

修改goods.html表格行的修改按钮

 <a href="goods_edit.html#?id={{entity.id}}" class="btn bg-olive btn-xs">修改</a>

(2)由商品编辑页跳转到商品列表

修改goods_edit.html 的返回列表按钮

<a href="goods.html" class="btn btn-default">返回列表</a>

(3)保存成功后返回列表页面

	//保存 
	$scope.save=function(){		
		.....		
		serviceObject.success(
			function(response){
				if(response.success){					
					location.href="goods.html";//跳转到商品列表页
				}else{
					alert(response.message);
				}
			}		
		);				
	}	

3.运营商后台-商品管理【商品审核】

3.1待审核商品列表

需求:参照商家后台商品列表。代码:

(1)修改pinyougou-manager-web的goodsController.js,注入itemCatService,添加代码

	$scope.status=['未审核','已审核','审核未通过','关闭'];//商品状态
	$scope.itemCatList=[];//商品分类列表
	//查询商品分类
	$scope.findItemCatList=function(){
		itemCatService.findAll().success(
			function(response){
				for(var i=0;i<response.length;i++){
					$scope.itemCatList[response[i].id ]=response[i].name;		
				}					
			}		
		);		
	}

(2)修改goods.html ,引入js

<script type="text/javascript" src="../plugins/angularjs/angular.min.js"></script>
<!-- 分页组件开始 -->
<script src="../plugins/angularjs/pagination.js"></script>
<link rel="stylesheet" href="../plugins/angularjs/pagination.css">
<!-- 分页组件结束 -->
<script type="text/javascript" src="../js/base_pagination.js"></script>	
<script type="text/javascript" src="../js/service/goodsService.js"></script>
<script type="text/javascript" src="../js/service/itemCatService.js"></script>
<script type="text/javascript" src="../js/controller/baseController.js"></script>
<script type="text/javascript" src="../js/controller/goodsController.js"></script>

(3)指令,完成初始调用

<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController" ng-init="searchEntity={auditStatus:'0'};findItemCatList()">

(4)循环列表

<tr ng-repeat="entity in list">
 <td><input type="checkbox"></td>			 
 	<td>{{entity.id}}</td>
 <td>{{entity.goodsName}}</td>
 <td>{{entity.price}}</td>
 <td>{{itemCatList[entity.category1Id]}}</td>
	<td>{{itemCatList[entity.category2Id]}}</td>
	<td>{{itemCatList[entity.category3Id]}}</td>
 <td>{{status[entity.auditStatus]}}</td>		 
 <td class="text-center"> </td>
</tr>

(5)分页控件

 <tm-pagination conf="paginationConf"></tm-pagination>

3.2商品详情展示(学员实现)

需求:点击列表右侧的“详情”按钮,弹出窗口显示商品信息。代码略。

3.3商品审核与驳回

需求:商品审核的状态值为1,驳回的状态值为2 。用户在列表中选中ID后,点击审核或驳回,修改商品状态,并刷新列表。

3.3.1后端代码

(1)在pinyougou-sellergoods-interface的GoodsService.java新增方法定义

	/**
	 * 批量修改状态
	 * @param ids
	 * @param status
	 */
	public void updateStatus(Long []ids,String status);

(2)在pinyougou-sellergoods-service的GoodsServiceImpl.java实现该方法

	public void updateStatus(Long[] ids, String status) {
		for(Long id:ids){
			TbGoods goods = goodsMapper.selectByPrimaryKey(id);
			goods.setAuditStatus(status);
			goodsMapper.updateByPrimaryKey(goods);
		}
	}

(3)在pinyougou-shop-web的GoodsController.java新增方法

	/**
	 * 更新状态
	 * @param ids
	 * @param status
	 */
	@RequestMapping("/updateStatus")
	public Result updateStatus(Long[] ids, String status){		
		try {
			goodsService.updateStatus(ids, status);
			return new Result(true, "成功");
		} catch (Exception e) {
			e.printStackTrace();
			return new Result(false, "失败");
		}
	}

3.3.2前端代码

(1)修改pinyougou-manager-web的goodsService.js ,增加方法

	//更改状态
	this.updateStatus=function(ids,status){
		return $http.get('../goods/updateStatus.do?ids='+ids+"&status="+status);
	} 

(2)修改pinyougou-manager-web的goodsController.js ,增加方法

	//更改状态
	$scope.updateStatus=function(status){		
		goodsService.updateStatus($scope.selectIds,status).success(
			function(response){
				if(response.success){//成功
					$scope.reloadList();//刷新列表
					$scope.selectIds=[];//清空ID集合
				}else{
					alert(response.message);
				}
			}
		);		
	}

(3)修改pinyougou-manager-web的goods.html 页面,为复选框绑定事件指令

<input type="checkbox" ng-click="updateSelection($event,entity.id)" >

(4)修改页面上的审核通过和驳回按钮

 <button type="button" class="btn btn-default" title="审核通过" ng-click="updateStatus('1')"><i class="fa fa-check"></i> 审核通过</button>
<button type="button" class="btn btn-default" title="驳回" ng-click="updateStatus('2')" ><i class="fa fa-ban"></i> 驳回</button>

4.运营商后台-商品管理【商品删除】

4.1需求分析

我们为商品管理提供商品删除功能,用户选中部分商品,点击删除按钮即可实现商品删除。注意,这里的删除并非是物理删除,而是修改tb_goods表的is_delete字段为1 ,我们可以称之为“逻辑删除”

4.2逻辑删除的实现

4.2.1后端代码

修改pinyougou-sellergoods-service工程的GoodsServiceImpl.java的delete方法

	/**
	 * 批量删除
	 */
	@Override
	public void delete(Long[] ids) {
		for(Long id:ids){
			TbGoods goods = goodsMapper.selectByPrimaryKey(id);
			goods.setIsDelete("1");
			goodsMapper.updateByPrimaryKey(goods);
		}		
	}

4.2.2前端代码

修改pinyougou-manager-web的goods.html上的删除按钮

<button type="button" class="btn btn-default" title="删除" ng-click="dele()"><i class="fa fa-trash-o"></i> 删除</button>

4.3排除已删除记录

修改pinyougou-sellergoods-service工程GoodsServiceImpl.java的findPage方法,添加以下代码:

criteria.andIsDeleteIsNull();//非删除状态

5.商家后台-【商品上下架】(学员实现)

5.1需求分析

什么是商品上下架?其实上下架也是商品的一个状态,但是不同于审核状态。审核状态的控制权在运营商手中,而上下架的控制权在商户手中。商户可以随时将一个已审核状态的商品上架或下架。上架表示正常销售,而下架则表示暂停销售。

5.2实现思路提示

其实商品的上下架就是对上下架状态的修改。字段为tb_goods表的is_marketable字段。1表示上架、0表示下架。

6.注解式事务配置

6.1事务异常测试

我们修改pinyougou-sellergoods-service工程GoodsServiceImpl.java的add方法

	/**
	 * 增加
	 */
	@Override
	public void add(Goods goods) {
		goods.getGoods().setAuditStatus("0");		
		goodsMapper.insert(goods.getGoods());	//插入商品表
		int x=1/0;
		goods.getGoodsDesc().setGoodsId(goods.getGoods().getId());
		goodsDescMapper.insert(goods.getGoodsDesc());//插入商品扩展数据
		saveItemList(goods);//插入商品SKU列表数据
	}

在插入商品表后,人为制造一个异常。我们运行程序,新增商品数据,观察运行结果。

通过观察,我们发现,程序发生异常 ,商品表仍然会存储记录,这是不符合我们要求的。这是因为我们目前的系统还没有配置事务。

6.2注解式事务解决方案

6.2.1配置文件

在pinyougou-sellergoods-service工程的spring目录下创建applicationContext-tx.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:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 <!-- 事务管理器 --> 
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
 <property name="dataSource" ref="dataSource" /> 
 </bean> 
 <!-- 开启事务控制的注解支持 --> 
 <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

6.2.2在方法上添加注解

/**
 * 服务实现层
 * @author Administrator
 *
 */
@Service
@Transactional
public class GoodsServiceImpl implements GoodsService{
........
}

经过测试,我们发现,系统发生异常,商品表不会新增记录,事务配置成功。

删除掉测试代码int x=1/0

我们需要将所有涉及多表操作的服务类添加事务注解,例如SpecificationServiceImpl类

文总结了几点删除按钮的设计技巧,以防止误操作导致防止数据丢失。enjoy~

数据丢失是用户使用计算机时可能遇到的最大意外之一。他们不仅丢失了数据,还损失了投入的时间和金钱。对于企业而言,这可能意味着数百个工时和数千美元的损失。不要让这种情况发生在您的用户身上。

一项研究发现,人为错误导致30%的数据丢失。这意味着良好的用户体验设计可以防止这些意外发生。以下是一些预防技巧。

在确认操作中使用红色警告信号

当用户按下删除按钮时,请勿立即执行,有可能是用户误点击了按钮,您需提示用户通过确认页面确认操作。

删除按钮请勿使用如蓝色等常规颜色。应使用红色按钮警示用户即将触发危险性操作。红色具有很强的视觉警告提示,因其更容易吸引用户注意力。

避免正常操作使用红色按钮,否则即为告警用户。保留红色按钮仅用作删除操作。冷色更适合使用于正常的行为呼叫按钮,因为其警示作用较弱。

虽然红色按钮能警示大部分用户,但是有些用户也许会忽视。额外的视觉提示,能增加警示作用。尤其是有助于无法区分色差的色盲和弱视用户群。

若要加强警告信号,请在确认屏幕上添加代表删除操作的图标。例如,用户熟悉的删除图标是垃圾桶。当用户看到图标时,他们会将当前操作与删除相关联。

您可以通过在屏幕顶部添加红色条块来加强警告信号。现在此确认页面,用户可以看到三个红色警告信号,表明他们即将进行危险性操作。这使用户更加注意他们的行为和处境,以防按错按钮。

UX效益-打破惯性点击

用户使用移动应用程序次数越多,他们越有可能养成惯性点击的习惯。无意识惯性点击可以更快更容易完成任务,但也更容易点击删除按钮。

红色通常与警告和危险有联系,并且具有负面含义。我们常看到许多用红色来传达警告和危险的标志。在设计中,红色按钮会引起用户对伤害或损失的恐惧,以防止错误。这是人类为了生存而躲避危险的本能。

研究表明,红色物体会引起注意并促进一致的运动反应。这意味着,当用户看到红色的删除按钮时,他们可能会更快、更准确地做出响应。用户对任务的关注越多,他们就会越好的执行该任务。

简洁的会话窗文本

红色警告信号可以防止删除意外,但这不是您须考虑的唯一事项。您还需编写简洁的会话文本来确保其易于浏览。

在会话窗标题的末尾添加问号来替代询问用户“您确定要删除吗?”。例如,标题为“删除帐户?”,用较少的字词表示“您确定要删除帐户吗?”。

不仅如此,不要使用冗长的句子来解释点击确认后会发生什么。以列表格式列出他们将丢失的内容,来替代通知用户,“如果您删除了自己的帐户,则会永久丢失您的个人资料,消息和照片”,以便用户快速阅览。

在该示例中,会话窗文本通过从25个单词减少到仅9个单词来简化。使得会话窗更容易浏览和理解。

UX效益更好地理解后果

确认对话框的目的是描述删除操作的后果。用户需要阅读并理解这些,否则他们可能会得到意想不到的结果。但在冗长的对话中很难做到这一点。

大多数用户会跳过冗长的文字,因为阅读需要花费时间和精力。简洁的文本可以防止用户跳过,帮助用户更快地执行任务,减少错误,并记住更多信息。使用简洁的对话文本,用户可以更好地理解其行为的后果并做出正确的决定。

居中对齐布局

简洁的文本使其容易浏览。但你还可以通过中心对齐布局使整个会话窗更进一步加强其易读性。中心对齐布局将图标与会话窗文本对齐,以便用户可以同时浏览。还使会话窗对称,图标更加突出,以防遮挡。

UX效益-减少视觉工作

当使用视觉追踪图观察一个左对齐布局和冗长的文本会话窗时,会发现有更多的注视点和更长的浏览路径。

简洁的会话窗和中心对齐的布局只需较少的视觉工作。通过更少的注视点和更短的浏览路径,用户可以更快地浏览屏幕以做出明智的决定。

  • 中心对齐布局可让用户以单一视觉方向(从上到下)浏览屏幕。他们不需要移动眼睛来浏览屏幕,只需要专注于屏幕的中心。
  • 使用左对齐布局,用户需要以两个可视方向(从左到右和从右到左)浏览屏幕。用户的眼睛需要做更多的工作,从而降低任务的完成效率。

确认保留红色警告信号

  • 当删除按钮出现在确认页面上时,您希望用户全神贯注。
  • 相反,当删除按钮未出现时,您不希望引起用户对它的注意。这样做会使用户无意点击时误点删除按钮。

如果删除按钮不在确认页面上时,请勿使用红色警告信号。例如,设置页面可以有一个“删除帐户”按钮,但它不需像呼叫行为按钮来吸引不必要的注意。

最好将删除按钮设为仅带有红色文本标签的独立按钮。使用过多的红色会导致用户将其误认为是屏幕上的主呼叫行为按钮。

UX效益-增加意外发生难度

用户在确认页面上的停留时间越多,他们按错按钮的可能性就越大。通过思考其他屏幕上的删除按钮,用户不太可能意外进入确认页面。这使得他们远离危险。

在其他页面上将删除按钮与正常按钮分开也可以使用户远离危险。用户不会将其误认为是正常的号召性按钮并想按下它。

提供撤消按钮

即使有确认屏幕,意外仍然可能发生。有些用户仍然可能误读了会话窗或按错了按钮。在确认屏幕之后,向用户提供撤消按钮,其中包含告知用户已执行操作的消息。

将撤消按钮和完成消息放在屏幕底部的通知横幅中。您可以根据删错操作的上下文使撤消按钮成为临时或持久的。

临时撤消将使横幅在几秒钟后自动消失。持久撤消显示横幅,直到用户通过按“关闭”按钮关闭横幅。请注意,持久性撤消的技术实现比临时撤消更复杂。

UX效益-允许用户从事故中恢复操作

撤消删除操作的选项允许用户从事故中恢复操作以防止数据丢失。数据丢失对企业和人们的生活造成严重后果。发布确认撤消按钮不仅可以保存用户的数据,还可以保存用户的工作。

提示用户点击确认

如果撤消按钮不是可选项,则可以提示用户在文本框中输入删除以确认。提示用户输入确认使他们意识到删除行为。虽然容易意外按错按钮,但是不可能输入意外出错,因为此操作需要很多步骤。

此方法对于用户经常使用删除操作的效率不高。例如,删除帖子是社交媒体应用上的常见操作。若要求用户每次通过输入来确认将大大降低用户操作效率。仅用于类型罕见的删除操作。

UX效益-确保用户确认意识

无意识地按下按钮比输入单词要容易得多。当用户输入时,他们必须考虑他们正在输入的内容然后点击右边的确认按钮。与按下按钮相比,出错空间更大。这使用户意识到他们的确认行为,以防止意外按下按钮。

数据丢失意外

当用户进入确认屏幕时,他们处于意外发生的边缘。如果您没有采取措施防止这种情况发生,按错了按钮可能会损坏数据。将这些方法应用到您的应用中,将避免用户遇到数据丢失意外。

PS:翻译过程中为适合我们的阅读习惯以及个人的理解,有对原文进行一定的内容简化和语义修饰,如有不妥欢迎大家根据官网链接进行比对并留言互动。

(本文翻译已获得该网站的正式授权)

原文链接:https://uxmovement.com/buttons/how-to-design-destructive-actions-that-prevent-data-loss/

原作者:anthony

译文地址:https://www.zcool.com.cn/article/ZMTAxNzcwMA==.html

编译作者:黎沫limo

本文由 @黎沫limo 翻译发布于人人都是产品经理,未经作者许可,禁止转载。

题图来自Unsplash,基于CC0协议。