Browse Source

更改

wangqin
zhangzhang 7 months ago
parent
commit
2efe6b57a7
  1. 21
      ruoyi-ui/src/api/commandDispatch/index.js
  2. 541
      ruoyi-ui/src/views/JiHeExpressway/pages/control/event/commandDispatch/Cards/DisposalProcess/DialogVisible/index.vue
  3. 5
      ruoyi-ui/src/views/JiHeExpressway/pages/control/event/commandDispatch/Cards/DisposalProcess/index.vue
  4. 49
      ruoyi-ui/src/views/JiHeExpressway/pages/control/event/dispatch/index.vue
  5. 12
      ruoyi-ui/src/views/JiHeExpressway/pages/service/PublishingChannelManagement/data.js

21
ruoyi-ui/src/api/commandDispatch/index.js

@ -99,7 +99,7 @@ export function addEventImportantFile(data) {
});
}
// 新增重要事件文件内容
// 修改重要事件文件内容
export function editEventImportantFile(data) {
return request({
url: "/business/eventImportantFile",
@ -120,7 +120,24 @@ export function downloadEventImportantFile(data) {
// 无分页查询事件列表
export function deventImportantFileList(eventId, type) {
return request({
url: `/business/eventImportantFile/list/?eventId=${eventId}/&type=${type}`,
url: `/business/eventImportantFile/list/?eventId=${eventId}&type=${type}`,
method: "get",
});
}
// 生成重要事件文件内容
export function getEventImportantFile(data) {
return request({
url: `/business/eventImportantFile/generate`,
method: "post",
data,
});
}
// 生成重要事件文件内容
export function importantFileStatus(eventId) {
return request({
url: `/business/eventImportantFile/getImportantFileStatus/${eventId}`,
method: "get",
});
}

541
ruoyi-ui/src/views/JiHeExpressway/pages/control/event/commandDispatch/Cards/DisposalProcess/DialogVisible/index.vue

@ -12,11 +12,6 @@
labelWidth="120px"
/>
<!-- <Form
:formList="formList"
:dFormData="formData"
label-width="100px"
/> -->
<span class="title">上报侧重要素</span>
<Form
ref="FormReportRef"
@ -24,7 +19,6 @@
v-model="reportData"
label-width="100px"
/>
<!-- :dFormData="formData" -->
<div class="footer">
<Button @click.native="onSubmit">保存</Button>
<!-- :loading="submitting" -->
@ -39,28 +33,262 @@
<el-tab-pane label="初报" name="second">
<div class="file" ref="file" id="export-box">
<div class="file-box">
<el-form>
<el-form :model="formFileData">
<div class="file-title">重要事件</div>
<div class="file-top">
<span class="file-top-fromDept"
>报送单位:
<el-input
:disabled="type != 0"
v-model="formFileData.fromDept"
/>
</span>
<span class="file-top-createTime"
>报送时间: {{ formFileData.createTime }}
</span>
</div>
<div class="file-content">
<div class="file-content-title">
<el-input :disabled="type != 0" v-model="formFileData.title">
</el-input>
</div>
<div class="file-content-toDept">
<el-input
:disabled="type != 0"
v-model="formFileData.toDept"
></el-input
>:
</div>
<el-input
:disabled="type != 0"
class="file-content-text"
v-model="formFileData.content"
type="textarea"
:rows="2"
placeholder="请输入内容"
></el-input>
</div>
<div class="file-footer">
<div class="file-createName">
<span>填报人:</span
><el-input
:disabled="type != 0"
v-model="formFileData.createName"
placeholder=""
></el-input>
</div>
<div class="file-phoneNumber">
<span>联系电话:</span
><el-input
:disabled="type != 0"
v-model="formFileData.phoneNumber"
placeholder=""
></el-input>
</div>
<div class="file-issuedr">
<span>签发人:</span
><el-input
:disabled="type != 0"
v-model="formFileData.issued"
placeholder=""
></el-input>
</div>
</div>
</el-form>
</div>
</div>
<div class="footer">
<Button
v-if="type == 0"
style="background: #39d5bf"
@click.native="handleInitData(1)"
>重新生成</Button
>
<Button style="background: #3de8ff" @click.native="handlePreview"
>预览</Button
>
<Button
v-if="type == 0"
style="background: #ffdb82"
@click.native="onFileSubmit"
>保存</Button
>
<Button v-if="type == 0" @click.native="onSubmitUpload"
>保存并上报</Button
>
<Button
style="background: #c9c9c9"
@click.native="modelVisible = false"
>关闭</Button
>
</div>
</el-tab-pane>
<el-tab-pane label="续报" name="third" :disabled="type == 0">
<div class="file" ref="file" id="export-box">
<!-- v-for="item in fileList" :key="item.id" -->
<div class="file-box">
<el-form :model="formFileData">
<div class="file-title">重要事件</div>
<div class="file-top">
<span class="file-top-fromDept"
>报送单位:
<el-input
:disabled="
type == 3 || (type == 2 && formFileData.status == 1)
"
v-model="formFileData.fromDept"
/>
</span>
<span class="file-top-createTime"
>报送时间: {{ formFileData.createTime }}
</span>
</div>
<div class="file-content">
<div class="file-content-title">
<el-input
:disabled="
type == 3 || (type == 2 && formFileData.status == 1)
"
v-model="formFileData.title"
>
</el-input>
</div>
<div class="file-content-toDept">
<el-input
:disabled="
type == 3 || (type == 2 && formFileData.status == 1)
"
v-model="formFileData.toDept"
></el-input
>:
</div>
<el-input
:disabled="
type == 3 || (type == 2 && formFileData.status == 1)
"
class="file-content-text"
v-model="formFileData.content"
type="textarea"
:rows="2"
placeholder="请输入内容"
></el-input>
</div>
<div class="file-footer">
<div class="file-createName">
<span>填报人:</span
><el-input
:disabled="
type == 3 || (type == 2 && formFileData.status == 1)
"
v-model="formFileData.createName"
placeholder=""
></el-input>
</div>
<div class="file-phoneNumber">
<span>联系电话:</span
><el-input
:disabled="
type == 3 || (type == 2 && formFileData.status == 1)
"
v-model="formFileData.phoneNumber"
placeholder=""
></el-input>
</div>
<div class="file-issuedr">
<span>签发人:</span
><el-input
:disabled="
type == 3 || (type == 2 && formFileData.status == 1)
"
v-model="formFileData.issued"
placeholder=""
></el-input>
</div>
</div>
</el-form>
</div>
</div>
<!-- @current-change="initData" @size-change="onSizeChange" -->
<Pagination
width="'100%'"
:page-size="pageSize"
:current-page.sync="pageNum"
layout="total, prev, pager, next"
:total="renewedList.length"
@size-change="onSizeChange"
@current-change="onCurrentChange"
>
</Pagination>
<div class="footer">
<Button
v-if="type != 3"
style="background: #39d5bf"
@click.native="handleInitData(2)"
>重新生成</Button
>
<Button style="background: #3de8ff" @click.native="handlePreview"
>预览</Button
>
<Button
v-if="
(type == 1 && formFileData.status == 0) ||
(newType == 1 && formFileData.status == 0)
"
style="background: #ffdb82"
@click.native="onFileSubmit"
>保存</Button
>
<Button
v-if="
(type == 1 && formFileData.status == 0) ||
(newType == 1 && formFileData.status == 0)
"
@click.native="onSubmitUpload"
>保存并上报</Button
>
<Button
style="background: #c9c9c9"
@click.native="modelVisible = false"
>关闭</Button
>
</div>
</el-tab-pane>
<el-tab-pane label="终报" name="fourth" :disabled="type == 0">
<div class="file" ref="file" id="export-box">
<div class="file-box">
<el-form :model="formFileData">
<div class="file-title">重要事件</div>
<div class="file-top">
<span class="file-top-fromDept"
>报送单位:
<el-input v-model="fromDept" />
<el-input
:disabled="type == 3"
v-model="formFileData.fromDept"
/>
</span>
<span class="file-top-createTime"
>报送时间: {{ createTime }}
>报送时间: {{ formFileData.createTime }}
</span>
</div>
<div class="file-content">
<div class="file-content-title">
<el-input v-model="title"> </el-input>
<el-input :disabled="type == 3" v-model="formFileData.title">
</el-input>
</div>
<div class="file-content-toDept">
<el-input v-model="toDept"></el-input>:
<el-input
:disabled="type == 3"
v-model="formFileData.toDept"
></el-input
>:
</div>
<el-input
:disabled="type == 3"
class="file-content-text"
v-model="content"
v-model="formFileData.content"
type="textarea"
:rows="2"
placeholder="请输入内容"
@ -69,31 +297,51 @@
<div class="file-footer">
<div class="file-createName">
<span>填报人:</span
><el-input v-model="createName" placeholder=""></el-input>
><el-input
:disabled="type == 3"
v-model="formFileData.createName"
placeholder=""
></el-input>
</div>
<div class="file-phoneNumber">
<span>联系电话:</span
><el-input v-model="phoneNumber" placeholder=""></el-input>
><el-input
:disabled="type == 3"
v-model="formFileData.phoneNumber"
placeholder=""
></el-input>
</div>
<div class="file-issuedr">
<span>签发人:</span
><el-input v-model="issued" placeholder=""></el-input>
><el-input
:disabled="type == 3"
v-model="formFileData.issued"
placeholder=""
></el-input>
</div>
</div>
</el-form>
</div>
</div>
<div class="footer">
<Button style="background: #39d5bf" @click.native="onSubmit"
<Button
v-if="type != 3"
style="background: #39d5bf"
@click.native="handleInitData(3)"
>重新生成</Button
>
<Button style="background: #3de8ff" @click.native="handlePreview"
>预览</Button
>
<Button style="background: #ffdb82" @click.native="onSubmit"
<Button
v-if="type != 3"
style="background: #ffdb82"
@click.native="onFileSubmit"
>保存</Button
>
<Button @click.native="onSubmit">保存并上报</Button>
<Button v-if="type != 3" @click.native="onSubmitUpload"
>保存并上报</Button
>
<Button
style="background: #c9c9c9"
@click.native="modelVisible = false"
@ -101,8 +349,6 @@
>
</div>
</el-tab-pane>
<el-tab-pane label="续报" name="third"> </el-tab-pane>
<el-tab-pane label="终报" name="fourth"> </el-tab-pane>
<Dialog style="" v-model="showDocx" width="1085px" top="60px">
<div class="docx-wrapper">
<section
@ -130,9 +376,9 @@
style="
font-family: 方正小标宋简体;
color: rgb(255, 0, 0);
min-height: 44pt;
font-size: 44pt;
font-weight: bold;
min-height: 46pt;
font-size: 46pt;
font-weight: 700;
"
>重要事件</span
>
@ -163,7 +409,8 @@
min-height: 10.5pt;
font-size: 10.5pt;
"
>山东高速青岛发展有限公司运营管理事业部 </span
>
{{ formFileData.fromDept }}</span
><span
lang="null"
style="
@ -181,7 +428,7 @@
min-height: 10.5pt;
font-size: 10.5pt;
"
>2024年05月09日15时40分</span
>{{ formFileData.createTime }}</span
>
</p>
<p
@ -221,7 +468,7 @@
font-size: 22pt;
font-weight: bold;
"
>关于G15沈海高速K502+222交通事故的初报</span
>{{ formFileData.title }}</span
>
</p>
<p
@ -243,42 +490,29 @@
>智慧管理中心</span
>
</p>
<p
<textarea
v-model="formFileData.content"
disabled
style="
width: 100%;
height: 40vh;
line-height: 1.5;
text-indent: 0pt;
vertical-align: baseline;
"
>
<span
lang="en-US"
style="
font-family: 仿宋_GB2312;
font-weight: normal;
color: black;
min-height: 16pt;
font-size: 16pt;
"
>
2024年05月09日11时02分G15沈海高速K502+222沈阳方向发生侧翻事故行1车道行2车道应急车道正常通行无压车</span
><span><br /></span
><span
lang="en-US"
style="
font-family: 仿宋_GB2312;
font-weight: normal;
color: black;
min-height: 16pt;
font-size: 16pt;
background: #fff;
border: none;
"
>
未到达现场的情况路管交警消防医疗等单位正在赶往现场具体情况稍后报告</span
>
</p>
<p style="vertical-align: baseline"><span id="_GoBack"></span></p>
<p style="vertical-align: baseline"></p>
<p style="vertical-align: baseline"></p>
<p style="vertical-align: baseline"></p>
{{ formFileData.content }}
</textarea>
<p
style="
line-height: 1;
@ -310,21 +544,20 @@
<p
class="docx-num-0-0"
style="
display: flex;
justify-content: space-between;
margin-top: 0pt;
text-align: center;
vertical-align: baseline;
"
>
<span
lang="en-US"
style="
font-family: 仿宋_GB2312;
font-weight: normal;
min-height: 14pt;
font-size: 14pt;
"
>填报人:智慧高速菜单 联系电话:18678866677 签发人郭璐明</span
>
<span lang="en-US">填报人:{{ formFileData.createName }} </span>
<span>联系电话:{{ formFileData.phoneNumber }}</span>
<span> 签发人{{ formFileData.issued }}</span>
</p>
</article>
</section>
@ -343,12 +576,18 @@ import { formList, reportList } from "./data";
import { tabConfigList } from "../../EventInformation/components/EditEventInformationDialog/data.js";
import Button from "@screen/components/Buttons/Button.vue";
import request from "@/utils/request";
import Pagination from "@screen/components/Pagination.vue";
import {
getEventImportantFile,
getEventImportant,
editEventImportant,
deventImportantFileList,
addEventImportantFile,
editEventImportantFile,
importantFileStatus,
} from "@/api/commandDispatch";
import { delay, exportFile, confirm } from "@screen/utils/common";
var moment = require("moment");
export default {
name: "EventDetail",
@ -356,6 +595,7 @@ export default {
Dialog,
Form,
Button,
Pagination,
},
model: {
prop: "visible",
@ -371,7 +611,8 @@ export default {
},
data() {
return {
// formList,
type: 0,
newType: 0,
showDocx: false,
fData: {},
formConfigList: [],
@ -389,6 +630,7 @@ export default {
chemicalsStatus: "",
fireStatus: "",
},
formFileData: {
fromDept: "齐鲁高速股份有限公司",
createTime: "2024年04月24日17时22分",
title: "关于G20青银高速K305+208交通事故的续报1",
@ -397,6 +639,12 @@ export default {
createName: "张三",
phoneNumber: "18678866677",
issued: "康传刚",
status: 0,
id: null,
},
pageSize: 1,
pageNum: 1,
renewedList: [],
// submitting: false,
};
},
@ -421,12 +669,9 @@ export default {
});
if (infoData.code != 200) return Message.error(infoData?.msg);
// this.resultEventData = { ...infoData.data };
// console.log("666", this.resultEventData);
let info = infoData.data;
if (typeof info.stakeMark == "string") {
let numbers = info.stakeMark.match(/\d+/g).map(Number);
// console.log('numbers', numbers);
info.stakeMark = numbers;
}
this.fData = info;
@ -484,8 +729,7 @@ export default {
);
})
.catch((err) => {
console.log("err", err);
Message.error("查询失败6", err);
Message.error("查询失败", err);
}),
]);
@ -502,21 +746,20 @@ export default {
});
},
getReportData() {
console.log("事件id", this.eventId);
getEventImportant(this.eventId).then((res) => {
console.log("详情", res);
this.reportData = { ...res.data };
});
},
handleClick(tab, event) {
this.activeName = tab.name;
if (this.activeName == "second") {
deventImportantFileList(this.eventId, 1).then((res) => {
console.log("res", res);
});
this.getEventImportantFileList(1);
} else if (this.activeName == "third") {
this.getEventImportantFileList(2);
} else if (this.activeName == "fourth") {
this.getEventImportantFileList(3);
}
},
getProcess() {
let directionFlg = true;
@ -559,12 +802,10 @@ export default {
});
});
},
//
onSubmit() {
this.$refs.FormConfigRef.validate().then((formData) => {
console.log("事件修改", formData);
});
this.$refs.FormConfigRef.validate().then((formData) => {});
this.$refs.FormReportRef.validate().then((formData) => {
console.log("事件侧重要素修改", formData);
formData.eventId = this.eventId;
editEventImportant(formData).then((result) => {
if (result.code != 200) return Message.error(result?.msg);
@ -573,28 +814,123 @@ export default {
});
});
},
//
getImportantFileStatus() {
importantFileStatus(this.eventId).then((res) => {
this.type = res.data;
console.log("事件状态", res.data);
});
},
//
getEventImportantFileList(type) {
deventImportantFileList(this.eventId, type).then((res) => {
if (res.length > 0) {
this.formFileData = res[0];
if (type == "2") {
this.renewedList = res;
}
} else {
this.handleInitData(type);
}
});
},
//
handleInitData(type) {
let data = new FormData();
data.append("eventId", this.eventId);
data.append("type", type);
getEventImportantFile(data).then((res) => {
this.formFileData = res.data;
if (type == "2") {
this.newType = 1;
this.renewedList.push(res.data);
this.pageNum = this.renewedList.length;
console.log("this.renewedList", this.renewedList);
}
});
},
//
onCurrentChange(val) {
console.log(`当前页: ${val}`);
this.renewedList.forEach((item, index) => {
if (val == index + 1) {
this.formFileData = item;
}
});
},
onSizeChange(pageSize) {
console.log("pageSize", pageSize);
},
//
handlePreview() {
this.showDocx = true;
},
//
handleDownload() {
console.log("下载");
// HTML
const fileContent = this.$refs.initFile.innerHTML;
// // HTMLDOCX
const doc = htmlDocx.asBlob(fileContent);
// //
FileSaver.saveAs(doc, "初报.docx");
exportFile({
url: "/business/eventImportantFile/download",
filename: this.type == 0 ? "初报" : this.type == 1 ? "续报" : "终报",
data: this.formFileData,
ext: "docx",
});
},
//
onFileSubmit() {
let data = {
...this.formFileData,
status: 0,
};
if (this.formFileData && this.formFileData.id) {
editEventImportantFile(data).then((res) => {});
} else {
addEventImportantFile(data).then((res) => {});
}
if (this.activeName == "second") {
this.getEventImportantFileList(1);
} else if (this.activeName == "third") {
this.getEventImportantFileList(2);
} else if (this.activeName == "fourth") {
this.getEventImportantFileList(3);
}
},
//
onSubmitUpload() {
let data = {
...this.formFileData,
status: 1,
};
if (this.formFileData && this.formFileData.id) {
editEventImportantFile(data).then((res) => {});
} else {
addEventImportantFile(data).then((res) => {});
}
this.getImportantFileStatus();
if (this.activeName == "second") {
this.getEventImportantFileList(1);
} else if (this.activeName == "third") {
this.getEventImportantFileList(2);
} else if (this.activeName == "fourth") {
this.getEventImportantFileList(3);
}
},
},
mounted() {
this.initData();
this.getReportData();
this.getImportantFileStatus();
},
};
</script>
<style lang="scss" scoped>
::v-deep .el-input.is-disabled input.el-input__inner {
color: #000 !important;
background: none;
}
.EventDetail {
display: flex;
gap: 9px;
@ -604,13 +940,15 @@ export default {
.title {
margin: 10px 0;
}
.footer {
display: flex;
justify-content: end;
gap: 15px;
margin-top: 20px;
margin-right: 20px;
}
// .footer {
// display: flex;
// justify-content: end;
// gap: 15px;
// margin-top: 20px;
// margin-right: 20px;
// div {
// }
// }
}
.file {
// width: 80%;
@ -653,6 +991,7 @@ export default {
border-image: initial;
background: #fff;
color: #000;
padding: 0;
}
}
@ -687,7 +1026,11 @@ export default {
}
}
.file-content {
.file-content-title .el-input {
width: 100% !important;
}
::v-deep .file-content-title .el-input__inner {
width: 100% !important;
font-family: AlibabaPuHuiTi-Medium, AlibabaPuHuiTi;
font-weight: 600;
text-align: center;
@ -744,14 +1087,21 @@ export default {
}
}
.footer {
display: flex;
width: 35%;
margin: auto;
margin-top: 30px;
justify-content: space-around;
// display: flex;
// flex-flow: row-reverse;
// max-width: 40%;
// width: 18%;
// margin: auto;
// margin-top: 30px;
// justify-content: space-around;
display: flex;
justify-content: end;
gap: 15px;
margin-top: 20px;
margin-right: 20px;
div {
// width: 120px;
// height: 30px;
// line-height: 30px;
margin-left: 20px;
}
}
@ -759,6 +1109,11 @@ export default {
overflow: auto;
position: relative;
}
::v-deep .el-tabs__item.is-disabled {
color: #c0c4cc !important;
cursor: default;
}
.docx-wrapper {
background: #fff;
padding: 30px;

5
ruoyi-ui/src/views/JiHeExpressway/pages/control/event/commandDispatch/Cards/DisposalProcess/index.vue

@ -10,8 +10,9 @@
无需清障
</ButtonGradient>
<ButtonGradient class="title-button" @click="handleClick">
重要事件上报
重要事件上报||已初报待续报||重要事件已送报
</ButtonGradient>
<!-- <ButtonGradient class="title-button"> 路赔 </ButtonGradient> -->
<ButtonGradient
class="title-button special-button"
@ -132,7 +133,7 @@ export default {
data() {
return {
dialogVisible: false,
eventId: "1a91d65cc31f4a9d90122888edb31043",
eventId: "f537723231d44eb59df27d81b3341d8d",
test: null,
testRadio: "input",
timeLine1List: [],

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

@ -9,27 +9,52 @@
刷新
</ButtonGradient>
<InputSearch ref="searchComp" style="width: 402px" :formList="searchFormList"
:formConfigOptions="{ dFormData: { eventState: '0' } }" @handleSearch="handleSearch" />
<InputSearch
ref="searchComp"
style="width: 402px"
:formList="searchFormList"
:formConfigOptions="{ dFormData: { eventState: '0' } }"
@handleSearch="handleSearch"
/>
</div>
<!-- 内容 -->
<div class="body">
<RoadStateCard v-for="(item, index) in data" :key="index" :cardData="item" :isShowLeft="false" @firstBtnClick="firstBtnClick"
@lastBtnClick="lastBtnClick" />
<RoadStateCard
v-for="(item, index) in data"
:key="index"
:cardData="item"
:isShowLeft="false"
@firstBtnClick="firstBtnClick"
@lastBtnClick="lastBtnClick"
/>
</div>
<!-- 分页 -->
<div class="footer">
<Pagination @current-change="initData" @size-change="onSizeChange" width="'100%'"
:page-sizes="[12, 16, 20, 30, 50]" :page-size="searchData.pageSize" :current-page.sync="searchData.pageNum"
layout="total, sizes, prev, pager, next" :total="total">
<Pagination
@current-change="initData"
@size-change="onSizeChange"
width="'100%'"
:page-sizes="[12, 16, 20, 30, 50]"
:page-size="searchData.pageSize"
:current-page.sync="searchData.pageNum"
layout="total, sizes, prev, pager, next"
:total="total"
>
</Pagination>
</div>
<EventDetailDialog :visible="eventDetailDialogVisible" :formData="detailDialogFormData"
@update:value="handleClose" />
<EventDispatchDialog :visible="eventDispatchDialogVisible" @update:value="handleClose" :eventId="eventId" />
<EventDetailDialog
:visible="eventDetailDialogVisible"
:formData="detailDialogFormData"
@update:value="handleClose"
/>
<EventDispatchDialog
:visible="eventDispatchDialogVisible"
@update:value="handleClose"
:eventId="eventId"
/>
</div>
</template>
@ -57,7 +82,6 @@ export default {
InputSearch,
EventDispatchDialog,
EventDetailDialog,
Pagination,
ButtonGradient,
},
data() {
@ -182,7 +206,8 @@ export default {
// justify-content: flex-end;
justify-content: space-between;
.refresh-btn {}
.refresh-btn {
}
}
.body {

12
ruoyi-ui/src/views/JiHeExpressway/pages/service/PublishingChannelManagement/data.js

@ -74,12 +74,12 @@ export const searchFormList = [
default: [],
options: {
options: [
{ key: "1", label: "微博", width: "87px" },
{ key: "3", label: "微信小程序", width: "87px" },
{ key: "4", label: "服务网站", width: "87px" },
{ key: "5", label: "情报板", width: "87px" },
{ key: "6", label: "手机短信", width: "87px" },
{ key: "7", label: "微信公众号", width: "87px" },
{ key: "1", label: "手机短信", width: "87px" },
{ key: "2", label: "微信公众号", width: "87px" },
{ key: "3", label: "微博", width: "87px" },
{ key: "4", label: "情报板", width: "87px" },
{ key: "5", label: "服务网站", width: "87px" },
{ key: "6", label: "微信小程序", width: "87px" },
],
},
},

Loading…
Cancel
Save