修复后台公告管理BUG以及搜索到数据之后删除所有搜到的数据,重载表格,数据不显示的BUG

lihaobo
yeqifu 6 years ago
parent 6289e20871
commit 67cd520ac0

@ -53,7 +53,7 @@
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -167,6 +167,13 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

@ -27,7 +27,7 @@ public class WarehouseApplication {
*/
factory.setMaxFileSize(DataSize.parse("102400KB"));
/**
*
* 6
*/
factory.setMaxRequestSize(DataSize.parse("102400KB"));
return factory.createMultipartConfig();

@ -1,12 +1,11 @@
package com.yeqifu.bus.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yeqifu.bus.entity.Goods;
import com.yeqifu.bus.entity.Sales;
import com.yeqifu.bus.mapper.GoodsMapper;
import com.yeqifu.bus.mapper.SalesMapper;
import com.yeqifu.bus.service.ISalesService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yeqifu.sys.common.ResultObj;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@ -23,13 +23,13 @@ import java.util.Map;
public class CachePool {
/**
* CACHE_CONTAINERredis
* CACHE_CONTAINER redis
*/
public static volatile Map<String,Object> CACHE_CONTAINER = new HashMap<>();
/**
* KEY
* @param key
* @param key
*/
public static void removeCacheByKey(String key){
if (CACHE_CONTAINER.containsKey(key)){

@ -21,7 +21,7 @@ public class AppFileUtils {
/**
*
*/
public static String UPLOAD_PATH="E:/upload/";
public static String UPLOAD_PATH="G:/upload/";
static {
//读取配置文件的存储地址

@ -36,6 +36,10 @@ public class ResultObj {
public static final ResultObj BACKINPORT_ERROR = new ResultObj(Constast.ERROR,"退货失败");
public static final ResultObj SYNCCACHE_SUCCESS = new ResultObj(Constast.OK,"同步缓存成功");
public static final ResultObj DELETE_ERROR_NEWS = new ResultObj(Constast.ERROR,"删除用户失败,该用户是其他用户的直属领导,请先修改该用户的下属的直属领导,再进行删除操作");
public static final ResultObj DELETE_QUERY = new ResultObj();
private Integer code;
private String msg;

@ -4,7 +4,6 @@ package com.yeqifu.sys.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sun.org.apache.regexp.internal.RE;
import com.yeqifu.sys.common.DataGridView;
import com.yeqifu.sys.common.ResultObj;
import com.yeqifu.sys.common.WebUtils;
@ -15,7 +14,6 @@ import com.yeqifu.sys.vo.NoticeVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.Serializable;
@ -59,6 +57,17 @@ public class NoticeController {
return new DataGridView(page.getTotal(),page.getRecords());
}
/**
* ID
* @param id ID
* @return
*/
@RequestMapping("loadNoticeById")
public DataGridView loadNoticeById(Integer id){
Notice notice = noticeService.getById(id);
return new DataGridView(notice);
}
/**
*
* @param noticeVo

@ -196,6 +196,21 @@ public class UserController {
}
}
/**
* ID
* @param userId
* @return
*/
@RequestMapping("queryMgrByUserId")
public ResultObj queryMgrByUserId(Integer userId){
Boolean isMgr = userService.queryMgrByUserId(userId);
if (isMgr){
return ResultObj.DELETE_ERROR_NEWS;
}else {
return ResultObj.DELETE_QUERY;
}
}
/**
*
* @param id

@ -19,4 +19,11 @@ public interface IUserService extends IService<User> {
* @param ids ID
*/
void saveUserRole(Integer uid, Integer[] ids);
/**
*
* @param userId
* @return
*/
Boolean queryMgrByUserId(Integer userId);
}

@ -1,16 +1,17 @@
package com.yeqifu.sys.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yeqifu.sys.entity.User;
import com.yeqifu.sys.mapper.RoleMapper;
import com.yeqifu.sys.mapper.UserMapper;
import com.yeqifu.sys.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.List;
/**
* <p>
@ -27,6 +28,9 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
@Autowired
private RoleMapper roleMapper;
@Autowired
private UserMapper userMapper;
@Override
public boolean save(User entity) {
return super.save(entity);
@ -66,4 +70,21 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
}
}
}
/**
*
* @param userId ID
* @return true: false:
*/
@Override
public Boolean queryMgrByUserId(Integer userId) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("mgr",userId);
List<User> users = userMapper.selectList(queryWrapper);
if (null!=users&&users.size()>0){
return true;
}else {
return false;
}
}
}

@ -2,8 +2,8 @@
spring:
datasource:
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/warehouse?useUnicode=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/warehouse?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC&useSSL=false
username: root
password: 123456
max-active: 20
@ -59,6 +59,6 @@ shiro:
#修改端口号为8088
server:
port: 8089
port: 8888

@ -1 +1 @@
filepath=E:/upload/
filepath=/G:/upload/

@ -101,7 +101,7 @@
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<input type="text" name="telephone" lay-verify="required" autocomplete="off" class="layui-input input-radius" placeholder="请输入客户电话">
<input type="text" name="telephone" lay-verify="required|number" autocomplete="off" class="layui-input input-radius" placeholder="请输入客户电话">
</div>
</div>
<div class="layui-inline">
@ -135,7 +135,7 @@
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<input type="text" id="account" name="account" lay-verify="required" autocomplete="off" class="layui-input input-radius" placeholder="请输入开户银行">
<input type="text" id="account" name="account" lay-verify="required|number" autocomplete="off" class="layui-input input-radius" placeholder="请输入开户银行">
</div>
</div>
</div>
@ -222,15 +222,6 @@
}
})
}
//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
if (data.data.length==0 && curr==1){
tableIns.reload({
where: "",
page:{
curr:1
}
})
}
}
});
@ -322,7 +313,9 @@
layer.confirm('' + data.customername + '', {icon: 3, title: ''}, function (index) {
$.post("/customer/deleteCustomer", {id: data.id},function (res) {
if (res.code == 200) {
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});
@ -350,7 +343,9 @@
$.post("/customer/batchDeleteCustomer",ids,function (res) {
if (res.code==200){
//重新刷新表格
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});

@ -157,49 +157,49 @@
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="text" class="layui-input input-radius" name="produceplace" placeholder="请输入商品产地">
<input type="text" class="layui-input input-radius" name="produceplace" lay-verify="required" placeholder="请输入商品产地">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="text" class="layui-input input-radius" name="goodspackage" placeholder="请输入商品包装">
<input type="text" class="layui-input input-radius" name="goodspackage" lay-verify="required" placeholder="请输入商品包装">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="text" class="layui-input input-radius" name="size" placeholder="请输入商品规格">
<input type="text" class="layui-input input-radius" name="size" lay-verify="required" placeholder="请输入商品规格">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="text" class="layui-input input-radius" name="productcode" placeholder="请输入商品生产批号">
<input type="text" class="layui-input input-radius" name="productcode" lay-verify="required" placeholder="请输入商品生产批号">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="text" class="layui-input input-radius" name="promitcode" placeholder="请输入商品批准文号">
<input type="text" class="layui-input input-radius" name="promitcode" lay-verify="required" placeholder="请输入商品批准文号">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="text" class="layui-input input-radius" name="price" placeholder="请输入商品销售价格">
<input type="text" class="layui-input input-radius" name="price" lay-verify="required|number" placeholder="请输入商品销售价格">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="text" class="layui-input input-radius" name="number" placeholder="请输入商品库存量">
<input type="text" class="layui-input input-radius" name="number" lay-verify="required|number" placeholder="请输入商品库存量">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<input type="text" class="layui-input input-radius" name="dangernum" placeholder="请输入商品预警值">
<input type="text" class="layui-input input-radius" name="dangernum" lay-verify="required|number" placeholder="请输入商品预警值">
</div>
</div>
<div class="layui-inline">
@ -277,15 +277,6 @@
}
})
}
//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
if (data.data.length==0 && curr==1){
tableIns.reload({
where: "",
page:{
curr:1
}
})
}
}
});
@ -435,7 +426,9 @@
layer.confirm('' + data.goodsname + '', {icon: 3, title: ''}, function (index) {
$.post("/goods/deleteGoods", {id: data.id,goodsimg:data.goodsimg},function (res) {
if (res.code == 200) {
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});

@ -158,13 +158,13 @@
<label class="layui-form-label"></label>
<div class="layui-input-block">
<input type="hidden" name="id">
<input type="text" name="number" lay-verify="required" autocomplete="off" class="layui-input input-radius" placeholder="请输入进货数量">
<input type="text" name="number" lay-verify="required|number" autocomplete="off" class="layui-input input-radius" placeholder="请输入进货数量">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<input type="text" name="inportprice" lay-verify="required" autocomplete="off" class="layui-input input-radius" placeholder="请输入进货价格">
<input type="text" name="inportprice" lay-verify="required|number" autocomplete="off" class="layui-input input-radius" placeholder="请输入进货价格">
</div>
</div>
</div>
@ -289,15 +289,6 @@
}
})
}
//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
if (data.data.length==0 && curr==1){
tableIns.reload({
where: "",
page:{
curr:1
}
})
}
}
});
@ -476,7 +467,9 @@
layer.confirm('', {icon: 3, title: ''}, function (index) {
$.post("/inport/deleteInport", {id: data.id},function (res) {
if (res.code == 200) {
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});

@ -174,15 +174,6 @@
}
})
}
//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
if (data.data.length==0 && curr==1){
tableIns.reload({
where: "",
page:{
curr:1
}
})
}
}
});
@ -238,7 +229,9 @@
layer.confirm('退', {icon: 3, title: ''}, function (index) {
$.post("/outport/deleteOutport", {id: data.id},function (res) {
if (res.code == 200) {
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});

@ -101,7 +101,7 @@
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<input type="text" name="telephone" lay-verify="required" autocomplete="off" class="layui-input input-radius" placeholder="请输入供应商电话">
<input type="text" name="telephone" lay-verify="required|number" autocomplete="off" class="layui-input input-radius" placeholder="请输入供应商电话">
</div>
</div>
<div class="layui-inline">
@ -135,7 +135,7 @@
<div class="layui-inline">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<input type="text" id="account" name="account" lay-verify="required" autocomplete="off" class="layui-input input-radius" placeholder="请输入开户银行">
<input type="text" id="account" name="account" lay-verify="required|number" autocomplete="off" class="layui-input input-radius" placeholder="请输入开户银行">
</div>
</div>
</div>
@ -222,15 +222,6 @@
}
})
}
//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
if (data.data.length==0 && curr==1){
tableIns.reload({
where: "",
page:{
curr:1
}
})
}
}
});
@ -322,7 +313,9 @@
layer.confirm('' + data.providername + '', {icon: 3, title: ''}, function (index) {
$.post("/provider/deleteProvider", {id: data.id},function (res) {
if (res.code == 200) {
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});
@ -350,7 +343,9 @@
$.post("/provider/batchDeleteProvider",ids,function (res) {
if (res.code==200){
//重新刷新表格
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});

@ -478,7 +478,9 @@
layer.confirm('', {icon: 3, title: ''}, function (index) {
$.post("/sales/deleteSales", {id: data.id},function (res) {
if (res.code == 200) {
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});

@ -229,7 +229,9 @@
layer.confirm('退', {icon: 3, title: ''}, function (index) {
$.post("/salesback/deleteSalesback", {id: data.id},function (res) {
if (res.code == 200) {
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});

@ -117,7 +117,9 @@
layer.confirm(''+data.key+'',{icon: 3,title:''},function (index) {
$.post("/cache/deleteCache",{key:data.key},function (res) {
if (res.code==200){
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});

@ -194,15 +194,6 @@
}
})
}
//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
if (data.data.length==0 && curr==1){
tableIns.reload({
where: "",
page:{
curr:1
}
})
}
}
});
@ -324,7 +315,9 @@
layer.confirm('' + data.name + '', {icon: 3, title: ''}, function (index) {
$.post("/dept/deleteDept", {id: data.id},function (res) {
if (res.code == 200) {
tableIns.reload();
tableIns.reload({
where:"",
});
//刷新下拉树
selectTree.reload();
//刷新左边的部门树

@ -29,6 +29,19 @@
</div>
</div>
<!-- div -->
<div id="desk_viewNewsDiv" style="padding: 10px;display: none;">
<h2 id="view_title" align="center"></h2>
<hr>
<div style="text-align: right;">
:<span id="view_opername"></span>
<span style="display: inline-block;width: 20px" ></span>
:<span id="view_createtime"></span>
</div>
<hr>
<div id="view_content"></div>
</div>
<script type="text/javascript" src="/resources/layui/layui.js"></script>
<script type="text/javascript">
@ -77,7 +90,7 @@
parent.addTab($(this));
})
//最新文章列表
$.get("/resources/json/newsList.json",function(data){
/*$.get("/resources/json/newsList.json",function(data){
var hotNewsHtml = '';
for(var i=0;i<5;i++){
hotNewsHtml += '<tr>'
@ -87,8 +100,42 @@
}
$(".hot_news").html(hotNewsHtml);
$(".userAll span").text(data.length);
})
})
})*/
$.get("/notice/loadAllNotice?page=1&limit=10",function(data){
var hotNewsHtml = '';
if(data.count>0){
for(var i=0;i<data.count;i++){
hotNewsHtml += '<tr ondblclick="viewNews('+data.data[i].id+')">'
+'<td align="left"><a href="javascript:;"> '+data.data[i].title+'</a></td>'
+'<td>'+data.data[i].createtime.substring(0,10)+'</td>'
+'</tr>';
}
}else {
hotNewsHtml+='<h3 style="text-align: center"></h3>';
}
$(".hot_news").html(hotNewsHtml);
$(".userAll span").text(data.length);
});
});
function viewNews(id){
$.get("/notice/loadNoticeById",{id:id},function(news){
layer.open({
type:1,
title:'',
content:$("#desk_viewNewsDiv"),
area:['800px','550px'],
success:function(index){
$("#view_title").html(news.data.title);
$("#view_opername").html(news.data.opername);
$("#view_createtime").html(news.data.createtime);
$("#view_content").html(news.data.content);
}
});
});
}
</script>
</body>

@ -125,15 +125,6 @@
}
})
}
//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
if (data.data.length==0 && curr==1){
tableIns.reload({
where: "",
page:{
curr:1
}
})
}
}
});
@ -173,7 +164,9 @@
layer.confirm('',{icon: 3,title:''},function (index) {
$.post("/loginfo/deleteLoginfo",{id:data.id},function (res) {
if (res.code==200){
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});
@ -201,7 +194,9 @@
$.post("/loginfo/batchDeleteLoginfo",ids,function (res) {
if (res.code==200){
//重新刷新表格
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});

@ -188,7 +188,7 @@
}
})
}
//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
/*//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
if (data.data.length==0 && curr==1){
tableIns.reload({
where: "",
@ -196,7 +196,7 @@
curr:1
}
})
}
}*/
}
});
@ -318,7 +318,9 @@
layer.confirm('' + data.title + '', {icon: 3, title: ''}, function (index) {
$.post("/menu/deleteMenu", {id: data.id},function (res) {
if (res.code == 200) {
tableIns.reload();
tableIns.reload({
where:"",
});
//刷新下拉树
selectTree.reload();
//刷新左边的菜单树

@ -164,23 +164,32 @@
{fixed: 'right', title: '', toolbar: '#noticeRowBar', align: 'center'}
] ],
done:function (data, curr, count) {
//不是第一页时如果当前返回的数据为0那么就返回上一页
if(data.data.length==0&&curr!=1){
/*if(data.count==0&&data.data.length==0){
tableIns.reload({
page:{
curr:curr-1
curr:1
}
})
}
//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
if (data.data.length==0 && curr==1){
}else*/
/* console.log(data);
console.log(curr); //1
console.log(count); //0*/
if(data.data.length==0&&curr!=1){ //不是第一页时如果当前返回的数据为0那么就返回上一页
tableIns.reload({
page:{
curr:curr-1
}
});
}/*else if (data.data.length==0 && curr==1){ //当前是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
tableIns.reload({
where: "",
page:{
curr:1
}
})
}
});
}*/
}
});
@ -232,7 +241,10 @@
layer.confirm(''+data.title+'',{icon: 3,title:''},function (index) {
$.post("/notice/deleteNotice",{id:data.id},function (res) {
if (res.code==200){
tableIns.reload();
tableIns.reload({
//删除成功加载表格时,要重置查询条件
where:"",
});
}
layer.msg(res.msg);
});
@ -260,7 +272,9 @@
$.post("/notice/batchDeleteNotice",ids,function (res) {
if (res.code==200){
//重新刷新表格
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});

@ -177,15 +177,6 @@
}
})
}
//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
if (data.data.length==0 && curr==1){
tableIns.reload({
where: "",
page:{
curr:1
}
})
}
}
});
@ -285,7 +276,9 @@
layer.confirm('' + data.title + '', {icon: 3, title: ''}, function (index) {
$.post("/permission/deletePermission", {id: data.id},function (res) {
if (res.code == 200) {
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});

@ -186,15 +186,6 @@
}
})
}
//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
if (data.data.length==0 && curr==1){
tableIns.reload({
where: "",
page:{
curr:1
}
})
}
}
});
@ -293,7 +284,9 @@
layer.confirm(''+data.name+'',{icon: 3,title:''},function (index) {
$.post("/role/deleteRole",{id:data.id},function (res) {
if (res.code==200){
tableIns.reload();
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});

@ -280,15 +280,6 @@
}
})
}
//当是第一页时如果当前返回的数据为0并且当前为第一页 给一个空的查询条件并给curr赋初值为1
if (data.data.length == 0 && curr == 1) {
tableIns.reload({
where: "",
page: {
curr: 1
}
})
}
}
});
@ -420,14 +411,22 @@
//删除
function deleteUser(data) {
layer.confirm('' + data.name + '', {icon: 3, title: ''}, function (index) {
// $.post("/user/deleteUser", {id: data.id}, function (res) {
$.post("/user/deleteUser/"+data.id, function (res) {
if (res.code == 200) {
tableIns.reload();
//查询该用户是否是其他用户的直属领导,如果是,提示删除失败
$.get("/user/queryMgrByUserId", {userId: data.id}, function (res) {
if (res.code==-1){
layer.msg(res.msg);
}else {
$.post("/user/deleteUser/"+data.id, function (res) {
if (res.code == 200) {
tableIns.reload({
where:"",
});
}
layer.msg(res.msg);
});
layer.close(index);
}
layer.msg(res.msg);
});
layer.close(index);
});
}

Loading…
Cancel
Save