Browse Source

Merge branch 'develop' of http://39.106.31.193:9211/mengff/jihe-hs into develop

wangqin
zhangzhang 11 months ago
parent
commit
c33d15b743
  1. 5
      ruoyi-ui/src/assets/styles/JiHeExpressway.scss
  2. 1
      ruoyi-ui/src/views/JiHeExpressway/components/Adaptation.vue
  3. 8
      ruoyi-ui/src/views/JiHeExpressway/mixins/InfoBoard.js
  4. 7
      ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/Dialogs/Camera/index.vue
  5. 11
      ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/HomeFilter/index.vue
  6. 10
      ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/HomeFrame/index.vue
  7. 8
      ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/HomeVector/index.vue
  8. 4
      ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/RoadAndEvents/utils/buttonEvent.js
  9. 8
      ruoyi-ui/src/views/JiHeExpressway/pages/control/event/event/index.vue
  10. 6
      ruoyi-ui/src/views/JiHeExpressway/pages/maintenanceOperations/statisticalAnalysis/index.vue
  11. 7
      ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/dailyDisposal/index.vue
  12. 8
      ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/dayTotal/index.vue
  13. 165
      ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/eventQuery/index.vue
  14. 9
      ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/eventSource/index.vue
  15. 8
      ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/monthStatistics/index.vue
  16. 6
      ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/railway/index.vue
  17. 8
      ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/railwayDay/index.vue
  18. 8
      ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/typeAnalysis/index.vue
  19. 96
      ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/index.vue
  20. 2
      ruoyi-ui/src/views/JiHeExpressway/pages/service/sensitive/index.vue
  21. 2
      ruoyi-ui/vue.config.js

5
ruoyi-ui/src/assets/styles/JiHeExpressway.scss

@ -1,9 +1,14 @@
.fl-col{ display: flex; flex-direction: column;}
.fl-1{ flex: 1;}
.theme-jihe {
$lightBlue: #3de8ff;
$textColor: #f4f4f4;
$inputHeight: 26px;
color: $textColor;
.is-light{
color: #666;
}
.text-center{ text-align: center;}
.tc-lb{ color: $lightBlue;}
.el-tabs__item {

1
ruoyi-ui/src/views/JiHeExpressway/components/Adaptation.vue

@ -53,6 +53,7 @@ export default {
},
watch: {
headerHeight() {
this.initScale();
}
},
methods: {

8
ruoyi-ui/src/views/JiHeExpressway/mixins/InfoBoard.js

@ -48,6 +48,12 @@ export default{
},
// 发布信息
____publishInfo() {
let deviceIdList = [];
if (this.selectedDevice){
deviceIdList = [this.selectedDevice.iotDeviceId];
}else{
deviceIdList = this.checkedDeviceIds;
}
this.$confirm('是否确定发布情报板?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@ -78,7 +84,7 @@ export default{
})
})
let data = { content: content, deviceIdList: this.checkedDeviceIds }
let data = { content: content, deviceIdList }
if (IS_TESTING) {
// this.saveLog(content);

7
ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/Dialogs/Camera/index.vue

@ -29,16 +29,15 @@
<Button style="margin-left: 5px" @click.native="controlClick(48)"></Button>
</div>
</ElTabPane>
<ElTabPane label="摄相机参数" name="second">摄相机参数</ElTabPane>
<!-- <ElTabPane label="摄相机参数" name="second">摄相机参数</ElTabPane> -->
<ElTabPane label="在线率统计" name="third">
<LineChart v-if="activeName === 'third'" :productId="dialogData.id" style="height: 180px" />
</ElTabPane>
</ElTabs>
<div class="bottom">
<!-- <Button>云台控制</Button> -->
<!-- <div class="bottom">
<Button> 录像查看</Button>
</div>
</div> -->
</div>
<CameraControlDialog :deviceId="dialogData.iotDeviceId" v-model="controlDialogVisible" />

11
ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/HomeFilter/index.vue

@ -2,9 +2,14 @@
<div class="HomeFilter">
<ElPopover trigger="manual" :value="activeIcon === 'filter'" :visibleArrow="false" placement="left"
popper-class="global-input-search-popover">
<Button :class="['btn', { 'btn-active': activeIcon }]" slot="reference" @click.native="handleClick('filter')">
<img src="@screen/images/home-filter/filter.svg" />
</Button>
<div slot="reference">
<el-tooltip effect="light" content="设备筛选" placement="left">
<Button :class="['btn', { 'btn-active': activeIcon }]" @click.native="handleClick('filter')">
<img src="@screen/images/home-filter/filter.svg" />
</Button>
</el-tooltip>
</div>
<div class="body">
<div class="title">设备筛选</div>

10
ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/HomeFrame/index.vue

@ -3,9 +3,13 @@
<ElPopover trigger="manual" :value="activeIcon === 'Frame'" :visibleArrow="false" placement="left"
popper-class="global-input-search-popover">
<Button :class="['btn', { 'btn-active': activeIcon }]" slot="reference" @click.native="handleClick('Frame')">
<img src="@screen/images/home-Frame/Frame.svg" />
</Button>
<div slot="reference">
<el-tooltip effect="light" content="图标含义" placement="left">
<Button :class="['btn', { 'btn-active': activeIcon }]" @click.native="handleClick('Frame')">
<img src="@screen/images/home-Frame/Frame.svg" />
</Button>
</el-tooltip>
</div>
<div class="body">
<div class="title">图标含义</div>

8
ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/HomeVector/index.vue

@ -1,8 +1,10 @@
<template>
<div class="HomeVector">
<Button :class="['btn', { 'btn-active': activeIcon }]" slot="reference" @click.native="handleClick('Vector')">
<img src="@screen/images/home-Vector/Vector.svg" />
</Button>
<el-tooltip effect="light" content="桩号显隐" placement="left">
<Button :class="['btn', { 'btn-active': activeIcon }]" @click.native="handleClick('Vector')">
<img src="@screen/images/home-Vector/Vector.svg" />
</Button>
</el-tooltip>
</div>
</template>

4
ruoyi-ui/src/views/JiHeExpressway/pages/Home/components/RoadAndEvents/utils/buttonEvent.js

@ -88,7 +88,9 @@ let debounceNoneLngLatMessage = debounce(() => {
function resolveDataOptions(data, config, component, isDefault) {
let lnglat =
data.longitude && data.latitude ? [data.longitude, data.latitude] : null;
data.longitude && data.latitude
? [parseFloat(data.longitude), parseFloat(data.latitude)]
: null;
if (!lnglat && !isDefault) debounceNoneLngLatMessage();

8
ruoyi-ui/src/views/JiHeExpressway/pages/control/event/event/index.vue

@ -225,12 +225,18 @@ export default {
this.isShowAddNew = true;
},
onExport() {
let url = '';
if (this.activeName == '-1') {
url = '/business/warning/export'
} else {
url = '/dc/system/event/export';
}
let loadingInstance = Loading.service({
fullscreen: true,
background: "#00000052",
text: "文件正在下载...",
});
request.post('/dc/system/event/export', {}, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, responseType: 'blob' })
request.post(url, {}, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, responseType: 'blob' })
.then((res) => {
console.log(res);
const url = window.URL.createObjectURL(new Blob([res]));

6
ruoyi-ui/src/views/JiHeExpressway/pages/maintenanceOperations/statisticalAnalysis/index.vue

@ -84,7 +84,7 @@
</el-table-column>
<el-table-column prop="deviceIp" label="设备IP"> </el-table-column>
<el-table-column prop="production" label="厂家"> </el-table-column>
<el-table-column prop="model" label="型号"> </el-table-column>
<!-- <el-table-column prop="model" label="型号"> </el-table-column> -->
<!-- <el-table-column prop="network" label="网段"> </el-table-column> -->
<el-table-column prop="time" label="监测时间"> </el-table-column>
<el-table-column prop="deviceStatus" label="设备状态">
@ -302,8 +302,8 @@ export default {
title: key,
total: val.sum,
pctOnl: val.sucessRate,
pctLose: val.failRate,
pctOffl: val.lostRate,
pctOffl: val.failRate,
pctLose: val.lostRate,
sumUseState: val.sumUseState
};
if (key.includes("全部设备")) {

7
ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/dailyDisposal/index.vue

@ -1,7 +1,7 @@
<template>
<div class="congestion">
<div class="congestion fl-col">
<WgtTitle :title="'日事件处置情况占比'"></WgtTitle>
<div class="board">
<div class="board fl-1">
<div class="charts keep-ratio" id="dailyDisposal"></div>
</div>
</div>
@ -154,11 +154,8 @@ export default {
<style lang="scss" scoped>
.congestion {
width: 100%;
.board {
height: 200px;
width: 100%;
padding: 0px 20px;
background: linear-gradient(180deg, rgba(6, 66, 88, 0.2) 0%, #064258 100%);
border-radius: 5px 5px 5px 5px;

8
ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/dayTotal/index.vue

@ -1,7 +1,7 @@
<template>
<div class="congestion">
<div class="congestion fl-col">
<WgtTitle :title="'日累计感知事件趋势'"></WgtTitle>
<div class="board">
<div class="board fl-1">
<Empty
v-show="!dataList || dataList.length <= 0"
text="暂无数据..."
@ -67,11 +67,7 @@ export default {
<style lang="scss" scoped>
.congestion {
width: 100%;
.board {
height: 200px;
width: 100%;
// padding: 0px 20px;
background: linear-gradient(180deg, rgba(6, 66, 88, 0.2) 0%, #064258 100%);
border-radius: 5px 5px 5px 5px;

165
ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/eventQuery/index.vue

@ -1,6 +1,6 @@
<template>
<div class="congestion">
<div class="board">
<div class="condition">
<ProgressBar
class="keep-ratio"
@selectItem="selectProgress"
@ -85,38 +85,29 @@
</div>
</div>
<div class="body">
<div class="comp_body">
<div>
<WgtTitle :title="'感知事件趋势分析'"></WgtTitle>
<!-- <Empty v-show="!chart1List || chart1List.length <= 0" text="暂无数据..."></Empty> -->
<!-- <div v-show="chart1List || chart1List.length > 0" id="chart1" class="btnChart" />-->
<div id="chart1" class="btnChart" />
<div class="unit_con">
<Empty v-if="!chart1List || chart1List.length <= 0" text="暂无数据..." class="empty"></Empty>
<div v-else id="chart1" class="chart_div" ></div>
</div>
</div>
<div>
<WgtTitle :title="'感知事件类型分析'"></WgtTitle>
<Empty
v-show="!chart2List || chart2List.length <= 0"
text="暂无数据..."
></Empty>
<div
v-show="chart2List || chart2List.length > 0"
id="chart2"
class="btnChart"
/>
<div class="unit_con">
<Empty v-show="!chart2List || chart2List.length <= 0" text="暂无数据..." class="empty"></Empty>
<div id="chart2" class="chart_div" ></div>
</div>
</div>
<div>
<WgtTitle :title="'桩号范围内事件分析趋势'"></WgtTitle>
<Empty
v-show="!chart3List || chart3List.length <= 0"
text="暂无数据..."
></Empty>
<div
v-show="chart3List || chart3List.length > 0"
id="chart3"
class="btnChart"
/>
<div class="unit_con">
<Empty v-show="!chart3List || chart3List.length <= 0" text="暂无数据..." class="empty"></Empty>
<div id="chart3" class="chart_div" ></div>
</div>
</div>
</div>
</div>
@ -418,7 +409,9 @@ export default {
chart1.series[0].data = numbers;
}
}
this.myChart1.setOption(chart1);
this.$nextTick(()=>{
this.initChart1();
})
});
//
@ -505,7 +498,9 @@ export default {
// this.myChart2.setOption(chart2);
// })
}
this.myChart2.setOption(chart2);
this.$nextTick(()=>{
this.initChart2();
})
});
//
getSectionMarkNumber({
@ -575,8 +570,9 @@ export default {
chart3.series[0].data = values1;
chart3.series[1].data = values2;
}
this.myChart3.setOption(chart3);
this.$nextTick(()=>{
this.initChart3();
})
});
// if (this.type == "day")
// this.type = "date";
@ -586,43 +582,18 @@ export default {
this.dateTime = moment(new Date()).format("YYYY-MM-DD");
}
},
initChart1(){
this.myChart1 = echarts.init(document.getElementById("chart1"));
this.myChart1.setOption(chart1);
},
mounted() {
setTimeout(() => {
this.$nextTick(() => {
getRoadSectionList().then((res) => {
console.log(res);
if (res.code == 200) {
let rows = res.data;
this.dataList = [];
rows.forEach((it) => {
this.dataList.push({
title: it.sectionName.split("-")[0],
id: it.id,
});
});
//
if (rows.length > 1) {
this.dataList.push({
title: rows[rows.length - 1].sectionName.split("-")[1],
id: rows[rows.length - 1].id,
});
}
console.log("dataList", this.dataList);
}
this.searchQuery();
});
var myChart1 = echarts.init(document.getElementById("chart1"));
myChart1.setOption(chart1);
var myChart2 = echarts.init(document.getElementById("chart2"));
myChart2.setOption(chart2);
var myChart3 = echarts.init(document.getElementById("chart3"));
myChart3.setOption(chart3);
initChart3(){
this.myChart3 = echarts.init(document.getElementById("chart3"));
this.myChart3.setOption(chart3);
},
initChart2(){
this.myChart1 = myChart1;
this.myChart2 = myChart2;
this.myChart3 = myChart3;
this.myChart2 = echarts.init(document.getElementById("chart2"));
this.myChart2.setOption(chart2);
const domMap = document.getElementById("chart2");
let parentDiv = domMap.firstChild;
@ -688,26 +659,48 @@ export default {
// gr.addColorStop(1, 'rgba(92,197,255,0)');
// gr.addColorStop(0, 'rgba(92,197,255,0.5)');
// drawRoundRect(context, 416, 208, 140, 24, 12, gr);
});
});
},
},
mounted() {
getRoadSectionList().then((res) => {
console.log(res);
if (res.code == 200) {
let rows = res.data;
this.dataList = [];
rows.forEach((it) => {
this.dataList.push({
title: it.sectionName.split("-")[0],
id: it.id,
});
});
//
if (rows.length > 1) {
this.dataList.push({
title: rows[rows.length - 1].sectionName.split("-")[1],
id: rows[rows.length - 1].id,
});
}
console.log("dataList", this.dataList);
}
this.searchQuery();
});
}
};
</script>
<style lang="scss" scoped>
.congestion {
width: 100%;
position: relative;
.body {
.comp_body {
width: 100%; height: 100%;
display: flex;
align-items: center;
justify-content: space-evenly;
align-items: stretch;
font-size: 14px;
> div {
width: 33%;
height: 470px;
flex: 1;
background: linear-gradient(
180deg,
rgba(6, 66, 88, 0.2) 0%,
@ -720,29 +713,22 @@ export default {
rgba(55, 231, 255, 0)
)
1 1;
display: flex; flex-direction: column; align-items: stretch;
margin-right: 14px;
&:last-child{ margin-right: 0;}
}
:nth-child(1) {
margin-right: 6px;
}
:nth-child(2) {
margin-left: 6px;
margin-right: 6px;
}
:nth-child(3) {
margin-left: 6px;
.unit_con {
flex: 1;
margin: 95px 20px 30px; position: relative;
}
.btnChart {
height: 330px;
width: 90%;
margin: 95px auto 0;
.empty{ position: absolute;}
.chart_div{
width: 100%; height: 100%;
}
}
.board {
.condition {
height: 100px;
width: 100%;
position: absolute;
@ -827,11 +813,6 @@ export default {
}
}
}
.charts {
height: 100px;
width: 100%;
}
</style>
<style lang="scss">

9
ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/eventSource/index.vue

@ -1,7 +1,7 @@
<template>
<div @resize="changeReisze" class="congestion">
<div @resize="changeReisze" class="congestion fl-col">
<WgtTitle :title="'感知事件源分析'"></WgtTitle>
<div class="board">
<div class="board fl-1">
<div class="charts keep-ratio" id="eventSource"></div>
</div>
</div>
@ -234,12 +234,7 @@ export default {
<style lang="scss" scoped>
.congestion {
width: 100%;
.board {
height: 200px;
width: 100%;
padding: 40px 40px;
background: linear-gradient(180deg, rgba(6, 66, 88, 0.2) 0%, #064258 100%);
border-radius: 5px 5px 5px 5px;
opacity: 1;

8
ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/monthStatistics/index.vue

@ -1,7 +1,7 @@
<template>
<div class="congestion">
<div class="congestion fl-col">
<WgtTitle :title="'近一月感知事件时段分布统计'"></WgtTitle>
<div class="board">
<div class="board fl-1">
<Empty
v-show="!dataList || dataList.length <= 0"
text="暂无数据..."
@ -75,11 +75,7 @@ export default {
<style lang="scss" scoped>
.congestion {
width: 100%;
.board {
height: 200px;
width: 100%;
// padding: 0px 20px;
background: linear-gradient(180deg, rgba(6, 66, 88, 0.2) 0%, #064258 100%);
border-radius: 5px 5px 5px 5px;

6
ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/railway/index.vue

@ -74,11 +74,9 @@ export default {
<style lang="scss" scoped>
.situation {
width: 100%;
width: 100%; height: 100%; display: flex; flex-direction: column;
.board {
height: 455px;
width: 100%;
.board { flex: 1; height: 0;
padding: 10px 10px;
background: linear-gradient(180deg, rgba(6, 66, 88, 0.2) 0%, #064258 100%);
border-radius: 5px 5px 5px 5px;

8
ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/railwayDay/index.vue

@ -1,7 +1,7 @@
<template>
<div class="congestion">
<div class="congestion fl-col">
<WgtTitle :title="'路段日感知事件'"></WgtTitle>
<div class="board">
<div class="board fl-1">
<Empty
v-show="!dataList || dataList.length <= 0"
text="暂无数据..."
@ -102,11 +102,7 @@ export default {
<style lang="scss" scoped>
.congestion {
width: 100%;
.board {
height: 200px;
width: 100%;
// padding: 0px 20px;
background: linear-gradient(180deg, rgba(6, 66, 88, 0.2) 0%, #064258 100%);
border-radius: 5px 5px 5px 5px;

8
ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/components/typeAnalysis/index.vue

@ -1,7 +1,7 @@
<template>
<div class="congestion">
<div class="congestion fl-col">
<WgtTitle :title="'感知事件类型分析'"></WgtTitle>
<div class="board">
<div class="board fl-1">
<div class="charts keep-ratio" id="typeAnalysis"></div>
</div>
</div>
@ -189,11 +189,7 @@ export default {
<style lang="scss" scoped>
.congestion {
width: 100%;
.board {
height: 200px;
width: 100%;
padding: 0px 20px;
background: linear-gradient(
180deg,

96
ruoyi-ui/src/views/JiHeExpressway/pages/perception/eventDetection/index.vue

@ -1,23 +1,21 @@
<template>
<div class="TrafficFlow">
<section class="content">
<section class="row_01">
<Railway class="content-l" />
<div class="content-m">
<RailWayDay class="content-m-t" />
<DayTotal class="content-m-t" />
<div class="content_r_g">
<RailWayDay class="content_r_unit" />
<DayTotal class="content_r_unit" />
</div>
<div class="content-m">
<EventSource class="content-m-t" />
<DailyDisposal class="content-m-t" />
<div class="content_r_g">
<EventSource class="content_r_unit" />
<DailyDisposal class="content_r_unit" />
</div>
<div class="content-m" style="margin-right: 0">
<MonthStatistice class="content-m-t" />
<TypeAnalysis class="content-m-t" />
<div class="content_r_g" style="margin-right: 0">
<MonthStatistice class="content_r_unit" />
<TypeAnalysis class="content_r_unit" />
</div>
</section>
<section class="foot">
<EventQuery class="foot-w" />
</section>
<EventQuery class="row_02" />
</div>
</template>
@ -27,7 +25,7 @@ import RailWayDay from "./components/railwayDay/index.vue";
import DayTotal from "./components/dayTotal/index.vue";
import EventSource from "./components/eventSource/index.vue";
import DailyDisposal from "./components/dailyDisposal/index.vue";
import TypeAnalysis from "./components/typeAnalysis";
import TypeAnalysis from "./components/typeAnalysis/index.vue";
import MonthStatistice from "./components/monthStatistics/index.vue";
import EventQuery from "./components/eventQuery";
@ -50,80 +48,44 @@ export default {
.TrafficFlow {
width: 100%;
height: 100%;
position: relative;
z-index: 6;
color: white;
.head {
width: 98%;
margin: auto;
margin-top: 15px;
}
.content {
width: 98%;
margin: auto;
display: flex;
flex: 1;
display: flex;
flex-direction: column;
align-items: stretch;
padding:8px 16px 16px;
.row_01 {
flex-basis: 55%; margin-bottom: 15px;
display: flex; align-items: stretch;
pointer-events: none;
margin-top: 19px;
> div {
pointer-events: auto;
}
.content-l {
width: calc(100% / 4 - 25px * 3);
min-width: 475px;
flex-basis: 25%;
margin-right: 15px;
}
.content-m {
display: inline-flex;
.content_r_g {
display: flex;
flex-direction: column;
width: calc(100% / 4);
flex-basis: 25%;
margin-right: 15px;
.content-m-t {
.content_r_unit {
width: 100%;
height: 240px;
flex-basis: 50%;
margin-bottom: 15px;
&:last-child{ margin-bottom: 0;}
}
}
.content-r {
width: 49.4%;
}
}
.foot {
width: 98%;
margin: auto;
display: flex;
justify-content: space-between;
.row_02 {
flex: 1;
pointer-events: none;
margin-top: 0px;
> div {
pointer-events: auto;
}
.foot-w {
width: 100%;
}
.foot-l {
width: 726px;
}
.foot-m {
width: 613px;
}
.foot-r {
width: 493px;
}
// pointer-events: none;
}
}
</style>

2
ruoyi-ui/src/views/JiHeExpressway/pages/service/sensitive/index.vue

@ -248,6 +248,8 @@ export default {
<style lang="scss" scoped>
.sensitiveWord {
width: 100%;
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;

2
ruoyi-ui/vue.config.js

@ -53,7 +53,7 @@ module.exports = {
// target: `http://10.168.69.255:8087`, //正晨后台 连现场物联 刘文阁
// target: `http://10.168.78.135:8087`, //王钦
// target: `http://10.168.66.196:8087`, //正晨后台 连现场物联 刘文阁2
target: `http://10.168.68.42:8087`, //王思祥
target: `http://10.168.65.156:8087`, //王思祥
// target: `http://10.168.65.194:8087`, //赵祥龙
// target: `http://10.168.65.156:8097`, //孟
// target: `http://10.168.56.165:8087`, //王家宝

Loading…
Cancel
Save