添加tags缓存

wp1.0
fanluyan 2 years ago
parent 0806ead733
commit d2249ca1d0

@ -1,10 +1,63 @@
<template> <template>
<div id="app"> <div id="app">
<router-view /> <router-view></router-view>
</div> </div>
</template> </template>
<style lang="less"> <script>
#app { import { mapActions } from "vuex";
}
</style> export default {
name: "App",
methods: {
...mapActions("cache", ["addCache", "removeCache"]),
//
collectCaches() {
//
this.$route.matched.forEach((routeMatch) => {
const instance = routeMatch.components?.default;
const componentName = instance?.name;
console.log(componentName);
if (process.env.NODE_ENV === "development") {
this.checkRouteComponentName(componentName, instance?.__file);
}
// meta.keepAlive
if (routeMatch.meta.keepAlive) {
if (!componentName) {
console.warn(`${routeMatch.path} 路由的组件名称name为空`);
return;
}
this.addCache(componentName);
} else {
this.removeCache(componentName);
}
});
},
//
checkRouteComponentName(name, file) {
if (!this.cmpNames) this.cmpNames = {};
if (this.cmpNames[name]) {
if (this.cmpNames[name] !== file) {
console.warn(
`${file}${this.cmpNames[name]} 组件名称重复: ${name}`
);
}
} else {
this.cmpNames[name] = file;
}
},
},
watch: {
"$route.path": {
immediate: true,
handler() {
this.collectCaches();
},
},
},
};
</script>
<style lang="less"></style>

@ -1,11 +1,15 @@
<template> <template>
<div class="wrapper"> <div class="wrapper">
<v-head></v-head> <v-head></v-head>
<layout-tabs></layout-tabs>
<div class="content-box"> <div class="content-box">
<div class="content"> <div class="content">
<transition name="move" mode="out-in"> <!-- <transition name="move" mode="out-in"> -->
<router-view></router-view> <keep-alive :include="caches">
</transition> <router-view v-if="isRenderTab"></router-view>
</keep-alive>
<!-- </transition> -->
</div> </div>
</div> </div>
</div> </div>
@ -13,13 +17,19 @@
<script> <script>
import vHead from "./header.vue"; import vHead from "./header.vue";
import LayoutTabs from "./LayoutTabs.vue";
import { mapState } from "vuex";
export default { export default {
data() { data() {
return {}; return {};
}, },
components: { components: {
vHead, vHead,
LayoutTabs,
},
computed: {
...mapState("cache", ["caches"]),
...mapState(["isRenderTab"]),
}, },
created() {}, created() {},
}; };
@ -31,7 +41,7 @@ export default {
position: absolute; position: absolute;
left: 0px; left: 0px;
right: 0; right: 0;
top: 56px; top: 92px;
bottom: 0; bottom: 0;
//padding-bottom: 30px; //padding-bottom: 30px;
-webkit-transition: left 0.3s ease-in-out; -webkit-transition: left 0.3s ease-in-out;

@ -0,0 +1,234 @@
<template>
<div class="layout-tabs">
<el-tabs
type="border-card"
v-model="curTabKey"
closable
@tab-click="clickTab"
@tab-remove="removeTab"
>
<el-tab-pane
v-for="item in tabs"
:label="item.title"
:name="item.tabKey"
:key="item.tabKey"
>
<template slot="label"
>{{ item.title }}
<i
v-if="curTabKey === item.tabKey"
class="el-icon-refresh"
@click="refreshTab(item)"
></i
></template>
</el-tab-pane>
</el-tabs>
<div class="close-tabs" @click="closeOtherTabs"></div>
</div>
</template>
<script>
import { mapMutations, mapActions } from "vuex";
import EventBus from "@/utils/event-bus";
export default {
name: "LayoutTabs",
props: {
// tab router-view
tabRouteViewDepth: {
type: Number,
default: 2,
},
// tabkeyroutekeytab
// matchRoute.path
getTabKey: {
type: Function,
default: function (routeMatch /* , route */) {
return routeMatch.path;
},
},
// tabmeta.title
tabTitleKey: {
type: String,
default: "title",
},
},
data() {
return {
tabs: [],
curTabKey: "",
};
},
methods: {
...mapActions("cache", ["addCache", "removeCache", "removeCacheEntry"]),
...mapMutations(["setIsRenderTab"]),
// tab
changeCurTab() {
//
const { path, query, params, hash, matched } = this.$route;
// tabmetacomponentName
const routeMatch = matched[this.tabRouteViewDepth - 1];
const meta = routeMatch.meta;
const componentName = routeMatch.components?.default?.name;
// tabtabKeykeytitle-tab-
const tabKey = this.getTabKey(routeMatch, this.$route);
const title = String(meta[this.tabTitleKey] || "");
const tab = this.tabs.find((tab) => tab.tabKey === tabKey);
if (!tabKey) {
// tabKeyname
console.warn(
`LayoutTabs组件${path} 路由没有匹配的tab标签页如有需要请配置tab标签页的key值`
);
return;
}
// route.path '/detail/:id'
// props.tabRouteViewDepth === matched.length tab
if (
tab &&
tab.path !== path &&
this.tabRouteViewDepth === matched.length
) {
this.removeCacheEntry(componentName || "");
tab.title = "";
}
const newTab = {
tabKey,
title: tab?.title || title,
path,
params,
query,
hash,
componentName,
};
tab ? Object.assign(tab, newTab) : this.tabs.push(newTab);
this.curTabKey = tabKey;
},
// tab
clickTab(pane) {
if (!pane.index) return;
const tab = this.tabs[Number(pane.index)];
if (tab.path !== this.$route.path) {
this.gotoTab(tab);
}
},
// tab
async removeTab(tabKey) {
//
if (this.tabs.length === 1) return;
const index = this.tabs.findIndex((tab) => tab.tabKey === tabKey);
if (index < -1) return;
const tab = this.tabs[index];
this.tabs.splice(index, 1);
// tabtab
if (tab.tabKey === this.curTabKey) {
const lastTab = this.tabs[this.tabs.length - 1];
lastTab && this.gotoTab(lastTab);
}
this.removeCache(tab.componentName || "");
},
// tab
async gotoTab(tab) {
await this.$router.push({
path: tab.path,
query: tab.query,
hash: tab.hash,
});
},
// tab
closeOtherTabs() {
this.tabs
.filter((tab) => tab.tabKey !== this.curTabKey)
.forEach((tab) => {
this.removeCache(tab.componentName || "");
});
this.tabs = this.tabs.filter((tab) => tab.tabKey === this.curTabKey);
},
// tab
async refreshTab(tab) {
this.setIsRenderTab(false);
await this.removeCacheEntry(tab.componentName);
this.setIsRenderTab(true);
},
// tab
async closeLayoutTab(tabKey = this.curTabKey) {
const index = this.tabs.findIndex((tab) => tab.tabKey === tabKey);
if (index > -1) {
this.removeCache(this.tabs[index].componentName);
this.tabs.splice(index, 1);
}
},
// tab
setCurTabTitle(title) {
const curTab = this.tabs.find((tab) => tab.tabKey === this.curTabKey);
if (curTab) {
curTab.title = title;
}
},
},
watch: {
"$route.path": {
handler() {
this.changeCurTab();
},
immediate: true,
},
},
created() {
// tab
EventBus.$on("LayoutTabs:closeTab", (tabKey) => {
this.closeLayoutTab(tabKey);
});
EventBus.$on("LayoutTabs:setTabTitle", (title) => {
this.setCurTabTitle(title);
});
},
};
</script>
<style lang="less">
.layout-tabs {
position: relative;
height: 32px;
line-height: 32px;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f5f7fa;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12), 0 0 6px 0 rgba(0, 0, 0, 0.04);
//background-color: #fcc;
.close-tabs {
padding-right: 12px;
cursor: pointer;
color: #999;
height: 32px;
line-height: 32px;
font-size: 12px;
&:hover {
color: #169e8c;
}
}
.el-tabs--border-card {
height: 30px;
flex: 1;
margin-right: 12px;
border: 1px solid #dcdfe6;
box-shadow: none;
}
.el-tabs__item {
height: 30px;
line-height: 30px;
font-size: 12px;
}
.el-tabs__content {
display: none;
}
}
</style>

@ -106,10 +106,10 @@ export default {
index: "/devicePhotoSchedule", index: "/devicePhotoSchedule",
title: "拍照时间表设置", title: "拍照时间表设置",
}, },
{ // {
index: "/deviceReport", // index: "/deviceReport",
title: "装置报表", // title: "",
}, // },
{ {
index: "/waterMark", index: "/waterMark",
title: "水印下发", title: "水印下发",

@ -8,135 +8,116 @@ const routes = [
{ {
path: "/stritl", path: "/stritl",
component: () => import("../components/Home.vue"), component: () => import("../components/Home.vue"),
meta: { title: "首页" },
children: [ children: [
{ {
path: "/stritl", path: "",
component: () => component: () => import("../views/homePage/index.vue"),
import( name: "home",
/* webpackChunkName: "dashboard" */ "../views/homePage/index.vue"
),
meta: { meta: {
title: "首页", title: "首页",
icon: "el-icon-s-home", icon: "el-icon-s-home",
// keepAlive: true,
}, },
}, },
{ {
path: "/realTimeMonitor", path: "/realTimeMonitor",
component: () => component: () => import("../views/realTimeMonitor/index.vue"),
import( name: "realTimeMonitor",
/* webpackChunkName: "home" */ "../views/realTimeMonitor/index.vue"
),
meta: { meta: {
title: "实时监控", title: "实时监控",
permission: true, permission: true,
icon: "el-icon-camera", icon: "el-icon-camera",
keepAlive: true,
}, },
}, },
{ {
path: "/pictureRotation", path: "/pictureRotation",
component: () => component: () => import("../views/pictureRotation/index.vue"),
import( name: "pictureRotation",
/* webpackChunkName: "home" */ "../views/pictureRotation/index.vue"
),
meta: { meta: {
title: "图片轮巡", title: "图片轮巡",
permission: true, permission: true,
icon: "el-icon-camera", icon: "el-icon-camera",
keepAlive: true,
}, },
}, },
{ {
path: "/realTimeSearch", path: "/realTimeSearch",
component: () =>
import( component: () => import("../views/realTimeSearch/index.vue"),
/* webpackChunkName: "home" */ "../views/realTimeSearch/index.vue" name: "realTimeSearch",
),
meta: { meta: {
title: "历史图片", title: "历史图片",
permission: true, permission: true,
icon: "el-icon-camera", icon: "el-icon-camera",
keepAlive: true,
}, },
}, },
{ {
path: "/photoAlarm", path: "/photoAlarm",
component: () => component: () => import("../views/alarmHandling/index.vue"),
import( name: "alarmHandling",
/* webpackChunkName: "home" */ "../views/alarmHandling/index.vue"
),
meta: { meta: {
title: "告警处理", title: "告警处理",
permission: true, permission: true,
icon: "el-icon-camera", icon: "el-icon-camera",
keepAlive: true,
}, },
}, },
{ {
path: "/userManagement", path: "/userManagement",
component: () => component: () => import("../views/system/userManagement.vue"),
import( name: "userManagement",
/* webpackChunkName: "tabs" */ "../views/system/userManagement.vue" meta: { title: "用户管理", icon: "el-icon-monitor", keepAlive: true },
),
meta: { title: "用户管理", icon: "el-icon-monitor" },
}, },
{ {
path: "/globalTools", path: "/globalTools",
component: () => component: () => import("../views/system/globalTools/index.vue"),
import( name: "globalTools",
/* webpackChunkName: "tabs" */ "../views/system/globalTools/index.vue" meta: { title: "全局设置", keepAlive: true },
),
meta: { title: "全局设置" },
}, },
{ {
path: "/lineInformation", path: "/lineInformation",
component: () => component: () => import("../views/lineInformation/index.vue"),
import( name: "lineInformation",
/* webpackChunkName: "tabs" */ "../views/lineInformation/index.vue" meta: { title: "线路信息管理", icon: "", keepAlive: true },
),
meta: { title: "线路信息管理", icon: "" },
}, },
{ {
path: "/towerInformation", path: "/towerInformation",
component: () => component: () => import("../views/towerInformation/index.vue"),
import( name: "towerInformation",
/* webpackChunkName: "tabs" */ "../views/towerInformation/index.vue" meta: { title: "杆塔信息管理", icon: "", keepAlive: true },
),
meta: { title: "杆塔信息管理", icon: "" },
}, },
{ {
path: "/cameraChannel", path: "/cameraChannel",
component: () => component: () => import("../views/cameraChannel/index.vue"),
import( name: "cameraChannel",
/* webpackChunkName: "tabs" */ "../views/cameraChannel/index.vue" meta: { title: "通道管理", icon: "", keepAlive: true },
),
meta: { title: "通道管理", icon: "" },
}, },
{ {
path: "/photographicDevice", path: "/photographicDevice",
component: () => component: () => import("../views/photographicDevice/index.vue"),
import( name: "photographicDevice",
/* webpackChunkName: "tabs" */ "../views/photographicDevice/index.vue" meta: { title: "拍照装置管理", icon: "", keepAlive: true },
),
meta: { title: "拍照装置管理", icon: "" },
}, },
{ {
path: "/devicePhotoSchedule", path: "/devicePhotoSchedule",
component: () => component: () => import("../views/devicePhotoSchedule/index.vue"),
import( name: "devicePhotoSchedule",
/* webpackChunkName: "tabs" */ "../views/devicePhotoSchedule/index.vue" meta: { title: "拍照时间表设置", icon: "", keepAlive: true },
),
meta: { title: "拍照时间表设置", icon: "" },
}, },
{ {
path: "/waterMark", path: "/waterMark",
component: () => component: () => import("../views/waterMark/index.vue"),
import(/* webpackChunkName: "tabs" */ "../views/waterMark/index.vue"), name: "waterMark",
meta: { title: "水印下发", icon: "" }, meta: { title: "水印下发", icon: "", keepAlive: true },
}, },
{ {
path: "/echarts", path: "/echarts",
component: () => component: () => import("../echartsDemo.vue"),
import(/* webpackChunkName: "tabs" */ "../echartsDemo.vue"), name: "echartsDemo",
meta: { title: "echarts图表", icon: "" }, meta: { title: "echarts图表", icon: "", keepAlive: true },
}, },
], ],
}, },

@ -0,0 +1,57 @@
import Vue from "vue";
export default {
namespaced: true,
state: {
caches: [],
},
actions: {
// 添加缓存的路由组件
addCache({ state, dispatch }, componentName) {
if (Array.isArray(componentName)) {
componentName.forEach((item) => {
dispatch("addCache", item);
});
return;
}
const { caches } = state;
if (!componentName || caches.includes(componentName)) return;
caches.push(componentName);
console.log("缓存路由组件:", componentName);
},
// 移除缓存的路由组件
removeCache({ state, dispatch }, componentName) {
if (Array.isArray(componentName)) {
componentName.forEach((item) => {
dispatch("removeCache", item);
});
return;
}
const { caches } = state;
const index = caches.indexOf(componentName);
if (index > -1) {
console.log("清除缓存的路由组件:", componentName);
return caches.splice(index, 1)[0];
}
},
// 移除缓存的路由组件的实例
async removeCacheEntry({ dispatch }, componentName) {
const cacheRemoved = await dispatch("removeCache", componentName);
if (cacheRemoved) {
await Vue.nextTick();
dispatch("addCache", componentName);
}
},
// 清除缓存的路由组件的实例
clearEntry({ state, dispatch }) {
const { caches } = state;
caches.slice().forEach((key) => {
dispatch("removeCacheEntry", key);
});
},
},
};

@ -1,6 +1,6 @@
import Vue from "vue"; import Vue from "vue";
import Vuex from "vuex"; import Vuex from "vuex";
import cacheModule from "./cache";
Vue.use(Vuex); Vue.use(Vuex);
export default new Vuex.Store({ export default new Vuex.Store({
@ -16,6 +16,7 @@ export default new Vuex.Store({
protocol: "", protocol: "",
cmdId: "", cmdId: "",
channelIdList: [], channelIdList: [],
isRenderTab: true,
}, },
getters: { getters: {
token: (state) => state.token, token: (state) => state.token,
@ -73,7 +74,10 @@ export default new Vuex.Store({
REMOVE_INFO(state) { REMOVE_INFO(state) {
localStorage.clear(); localStorage.clear();
}, },
setIsRenderTab(state, data) {
state.isRenderTab = data;
},
}, },
actions: {}, actions: {},
modules: {}, modules: { cache: cacheModule },
}); });

@ -0,0 +1,4 @@
import Vue from 'vue'
const EventBus = new Vue()
export default EventBus

@ -364,6 +364,7 @@ import {
import morePicPreveiw from "../realTimeMonitor/components/morePicPreveiw"; import morePicPreveiw from "../realTimeMonitor/components/morePicPreveiw";
import moment from "moment"; import moment from "moment";
export default { export default {
name: "alarmHandling",
components: { components: {
morePicPreveiw, morePicPreveiw,
}, },

@ -100,6 +100,7 @@ import addChannelDialog from "./components/addChannelDialog.vue";
import { getChannelListapi, deleteChannelapi } from "@/utils/api/index"; import { getChannelListapi, deleteChannelapi } from "@/utils/api/index";
export default { export default {
name: "cameraChannel",
components: { components: {
addChannelDialog, addChannelDialog,
}, },

@ -96,6 +96,7 @@ import adddeviceDialog from "./components/adddeviceDialog.vue";
import bdSchedule from "./components/bdSchedule.vue"; import bdSchedule from "./components/bdSchedule.vue";
export default { export default {
name: "devicePhotoSchedule",
components: { components: {
adddeviceDialog, adddeviceDialog,
bdSchedule, bdSchedule,

@ -126,7 +126,7 @@ import {
getOnlineTerminalListExcel, getOnlineTerminalListExcel,
} from "@/utils/api/index"; } from "@/utils/api/index";
export default { export default {
name: "", name: "home",
data() { data() {
return { return {
termDataNum: "", // termDataNum: "", //
@ -477,11 +477,11 @@ export default {
</script> </script>
<style lang="less"> <style lang="less">
.stritleEchartsPage { .stritleEchartsPage {
height: calc(100% - 32px); height: calc(100% - 24px);
padding: 16px; padding: 12px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-around; justify-content: space-between;
.echart-top { .echart-top {
display: flex; display: flex;
height: 48%; height: 48%;

@ -127,6 +127,7 @@ import { getLineListJoggle, deleteLineJoggle } from "@/utils/api/index";
import addLineDialog from "./components/addLineDialog.vue"; import addLineDialog from "./components/addLineDialog.vue";
import addTowerDialog from "./components/addTowerDialog.vue"; import addTowerDialog from "./components/addTowerDialog.vue";
export default { export default {
name: "lineInformation",
components: { components: {
addLineDialog, addLineDialog,
addTowerDialog, addTowerDialog,

@ -339,6 +339,7 @@ import gpsSite from "./components/gpsSite.vue";
import addLineDialog from "./components/addLineDialog.vue"; import addLineDialog from "./components/addLineDialog.vue";
import towerDialog from "./components/towerDialog.vue"; import towerDialog from "./components/towerDialog.vue";
export default { export default {
name: "photographicDevice",
components: { components: {
addPhotoDialog, addPhotoDialog,
pictureTags, pictureTags,

@ -163,6 +163,7 @@ import { getPictureList, getTerminalPhotoListJoggle } from "@/utils/api/index";
import morePicPreveiw from "../realTimeMonitor/components/morePicPreveiw"; import morePicPreveiw from "../realTimeMonitor/components/morePicPreveiw";
import defaultImage from "../../assets/img/nodatapic2.jpg"; import defaultImage from "../../assets/img/nodatapic2.jpg";
export default { export default {
name: "pictureRotation",
components: { components: {
morePicPreveiw, morePicPreveiw,
}, },

@ -40,6 +40,7 @@ import morePicPreveiw from "./components/morePicPreveiw";
import { mapGetters, mapState } from "vuex"; import { mapGetters, mapState } from "vuex";
export default { export default {
name: "realTimeMonitor",
data() { data() {
return { return {
LineFlag: false, //线 LineFlag: false, //线

@ -206,6 +206,7 @@
import { getSearchInfo, getRealtimePhoto } from "@/utils/api/index"; import { getSearchInfo, getRealtimePhoto } from "@/utils/api/index";
import defaultImage from "../../assets/img/nodatapic2.jpg"; import defaultImage from "../../assets/img/nodatapic2.jpg";
export default { export default {
name: "realTimeSearch",
data() { data() {
return { return {
pickerOptions: { pickerOptions: {

@ -50,9 +50,6 @@
> >
</el-card> </el-card>
</div> </div>
<div class="" v-for="item in infoMl">
{{ item }}
</div>
</div> </div>
</template> </template>
<script> <script>
@ -65,6 +62,7 @@ import {
} from "@/utils/api/index"; } from "@/utils/api/index";
export default { export default {
name: "globalTools",
components: {}, components: {},
data() { data() {
return { return {
@ -73,47 +71,11 @@ export default {
probList: [], probList: [],
tdOptions: [{ id: -1, name: "全部", alias: null }], // tdOptions: [{ id: -1, name: "全部", alias: null }], //
channel: "", channel: "",
infoMl: [],
tongdao: ["1", "2", "3", "4"],
cmdidArr: [
"12M10010107139801",
"12M10010107139802",
"12M10010107139803",
"12M10010107139804",
"12M10010107139805",
"12M10010107139806",
"12M10010107139807",
"12M10010107139808",
"12M10010107139809",
],
leftWater: [
"欣影-2023-10-08",
"欣影-2023-10-08",
"欣影-2023-10-08",
"欣影-2023-10-08",
"欣影-2023-10-08",
"欣影-2023-10-08",
"欣影-2023-10-08",
"欣影-2023-10-08",
"欣影-2023-10-08",
],
rightWater: [
"安庆- 输电运检一班-220kV文邓4C70线#052大号侧",
"安庆- 输电运检一班-220kV文邓4C70线#053大号侧",
"安庆- 输电运检一班-220kV文邓4C70线#053大号侧",
"安庆- 输电运检一班-220kV文邓4C70线#053大号侧",
"安庆- 输电运检一班-220kV文邓4C70线#053大号侧",
"安庆- 输电运检一班-220kV文邓4C70线#053大号侧",
"安庆- 输电运检一班-220kV文邓4C70线#053大号侧",
"安庆- 输电运检一班-220kV文邓4C70线#053大号侧",
"安庆- 输电运检一班-220kV文邓4C70线#053大号侧",
],
}; };
}, },
created() { created() {
this.getalarmList(); this.getalarmList();
this.getmark(); this.getmark();
this.getWater();
}, },
methods: { methods: {
// //
@ -195,37 +157,6 @@ export default {
console.log(err); console.log(err);
}); });
}, },
getWater() {
for (let j = 0; j < this.tongdao.length; j++) {
for (let i = 0; i < this.cmdidArr.length; i++) {
// console.log(this.cmdidArr[i]);
// console.log(this.leftWater[i]);
// console.log(this.rightWater[i]);
var a =
"/usr/local/bin/xympadmn --server=127.0.0.1 --port=6891 --act=osd --cmdid=" +
this.cmdidArr[i] +
" --flag=1 --channel=" +
this.tongdao[j] +
' --leftBottom="' +
this.leftWater[i] +
'" --rightBottom="' +
this.rightWater[i] +
'" --clientid=5 --reqid=TS; sleep 0.5';
this.infoMl.push(a);
console.log(
"/usr/local/bin/xympadmn --server=127.0.0.1 --port=6891 --act=osd --cmdid=" +
this.cmdidArr[i] +
" --flag=1 --channel=" +
this.tongdao[j] +
'" --leftBottom="' +
this.leftWater[i] +
'" --rightBottom="' +
this.rightWater[i] +
'" --clientid=5 --reqid=TS; sleep 0.5'
);
}
}
},
}, },
}; };
</script> </script>

@ -81,6 +81,7 @@ import addUser from "./components/addUser.vue";
import { getUserList, delUserApi } from "@/utils/api/index"; import { getUserList, delUserApi } from "@/utils/api/index";
export default { export default {
name: "userManagement",
components: { components: {
addUser, addUser,
}, },

@ -163,6 +163,7 @@ import { getTowerListApi, delTowerApi, getSearchInfo } from "@/utils/api/index";
import addDialog from "./components/addDialog.vue"; import addDialog from "./components/addDialog.vue";
export default { export default {
name: "towerInformation",
components: { components: {
addDialog, addDialog,
}, },

@ -63,7 +63,7 @@
</template> </template>
<script> <script>
export default { export default {
components: {}, name: "waterMark",
data() { data() {
return { return {
channelList: [ channelList: [

Loading…
Cancel
Save