Browse Source

[完善]完善基本信息页面

非煤矿山灾害智能感知和预警系统
邵佳豪 2 years ago
parent
commit
7bd43d7216
  1. 6
      src/app/CustomReuseStrategy.ts
  2. 153
      src/app/home/basic-info/add-unit/add-unit.component.html
  3. 25
      src/app/home/basic-info/add-unit/add-unit.component.spec.ts
  4. 86
      src/app/home/basic-info/add-unit/add-unit.component.ts
  5. 63
      src/app/home/basic-info/unit/unit.component.html
  6. 15
      src/app/home/basic-info/unit/unit.component.scss
  7. 165
      src/app/home/basic-info/unit/unit.component.ts
  8. 6
      src/app/home/task/da-oneself-plan/da-oneself-plan.component.html

6
src/app/CustomReuseStrategy.ts

@ -26,9 +26,9 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
/** 当路由离开时会触发。按path作为key存储路由快照&组件当前实例对象 */
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
// console.log('store======>route', route);
if (route.routeConfig.path == 'unit') {
CustomReuseStrategy.handlers[this.getRouteUrl(route)] = handle;
}
// if (route.routeConfig.path == 'unit') {
// CustomReuseStrategy.handlers[this.getRouteUrl(route)] = handle;
// }
}
/** 若 path 在缓存中有的都认为允许还原路由 */

153
src/app/home/basic-info/add-unit/add-unit.component.html

@ -1,73 +1,90 @@
<div class="box">
<form nz-form [formGroup]="validateForm">
<nz-form-item>
<nz-form-label [nzSm]="7" [nzXs]="24" nzRequired nzFor="单位名称">单位名称</nz-form-label>
<form nz-form [formGroup]="validateForm">
<nz-form-item>
<nz-form-label [nzSm]="7" [nzXs]="24" nzRequired nzFor="单位名称">单位名称</nz-form-label>
<nz-form-control>
<nz-input-group>
<input nz-input type="text" formControlName="unitname" placeholder="请输入单位名称" />
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSm]="7" [nzXs]="24" nzFor="消防安全责任人">消防安全责任人</nz-form-label>
<nz-form-control>
<nz-input-group>
<input nz-input type="text" formControlName="person" placeholder="请输入消防安全责任人" />
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSm]="7" [nzXs]="24" nzFor="联系方式">联系方式</nz-form-label>
<nz-form-control>
<nz-input-group>
<input nz-input type="text" formControlName="phone" placeholder="请输入联系方式" />
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSm]="7" [nzXs]="24" nzFor="单位地址">单位地址</nz-form-label>
<nz-form-control>
<nz-form-control>
<nz-input-group>
<input nz-input type="text" formControlName="unit" placeholder="请输入单位名称" />
<input nz-input type="text" formControlName="addr" placeholder="请输入单位地址" />
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSm]="7" [nzXs]="24" nzFor="消防安全责任人">消防安全责任人</nz-form-label>
<nz-form-control>
<nz-input-group>
<input nz-input type="text" formControlName="name" placeholder="请输入消防安全责任人" />
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSm]="7" [nzXs]="24" nzFor="联系方式">联系方式</nz-form-label>
<nz-form-control>
<nz-input-group>
<input nz-input type="text" formControlName="iphone" placeholder="请输入联系方式" />
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSm]="7" [nzXs]="24" nzFor="单位地址">单位地址</nz-form-label>
<nz-form-control>
<nz-form-control>
<nz-input-group>
<input nz-input type="text" formControlName="addr" placeholder="请输入单位地址" />
</nz-input-group>
</nz-form-control>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzRequired [nzSm]="7" [nzXs]="24" nzFor="所属救援站">所属救援站</nz-form-label>
<nz-form-control>
<nz-select formControlName="role" nzPlaceHolder="请选择所属救援站">
<nz-option *ngFor="let item of jiuyuanzhan" [nzValue]="item.id" [nzLabel]="item.name">
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item >
<nz-form-label [nzSm]="7" [nzXs]="24" nzFor="所属大队">所属大队</nz-form-label>
<nz-form-control>
<nz-select formControlName="role2" nzPlaceHolder="请选择所属大队" >
<nz-option *ngFor="let item of dadui" [nzValue]="item.id" [nzLabel]="item.name">
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzRequired [nzSm]="7" [nzXs]="24" nzFor="使用性质">使用性质</nz-form-label>
<nz-form-control>
<nz-input-group>
<input nz-input type="text" formControlName="nature" placeholder="请输入使用性质" />
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSm]="7" [nzXs]="24" nzFor="建筑类型">建筑类型</nz-form-label>
<nz-form-control>
<nz-select formControlName="role4" nzPlaceHolder="请选择建筑类型">
<nz-option *ngFor="let item of listOfData2" [nzValue]="item.id" [nzLabel]="item.name">
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
</form>
</div>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzRequired [nzSm]="7" [nzXs]="24" nzFor="所属救援站">所属救援站</nz-form-label>
<nz-form-control>
<nz-tree-select [nzNodes]="nodes" nzShowSearch nzPlaceHolder="所属机构" formControlName="orStation"
[nzExpandedIcon]="multiExpandedIconTpl" [nzDropdownClassName]="'maxHeightTreeSelect'" [nzAllowClear]="false">
</nz-tree-select>
<ng-template #multiExpandedIconTpl let-node let-origin="origin">
<ng-container *ngIf="node.children.length == 0; else elseTemplate">
</ng-container>
<ng-template #elseTemplate>
<i nz-icon [nzType]="node.isExpanded ? 'caret-down' : 'caret-right'"
class="ant-tree-switcher-line-icon"></i>
</ng-template>
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSm]="7" [nzXs]="24" nzFor="所属大队">所属大队</nz-form-label>
<nz-form-control>
<nz-tree-select [nzNodes]="nodes" nzShowSearch nzPlaceHolder="所属机构" formControlName="orDa"
[nzExpandedIcon]="multiExpandedIconTpl" [nzDropdownClassName]="'maxHeightTreeSelect'" [nzAllowClear]="false">
</nz-tree-select>
<ng-template #multiExpandedIconTpl let-node let-origin="origin">
<ng-container *ngIf="node.children.length == 0; else elseTemplate">
</ng-container>
<ng-template #elseTemplate>
<i nz-icon [nzType]="node.isExpanded ? 'caret-down' : 'caret-right'"
class="ant-tree-switcher-line-icon"></i>
</ng-template>
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzRequired [nzSm]="7" [nzXs]="24" nzFor="使用性质">使用性质</nz-form-label>
<nz-form-control>
<nz-select formControlName="nature" nzAllowClear nzPlaceHolder="请选择使用性质">
<nz-option nzValue="一般单位" nzLabel="一般单位"></nz-option>
<nz-option nzValue="重点单位" nzLabel="重点单位"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSm]="7" [nzXs]="24" nzFor="单位类型">单位类型</nz-form-label>
<nz-form-control>
<nz-select formControlName="unittype" nzPlaceHolder="请选择单位类型">
<nz-option *ngFor="let item of BuildingTypes" [nzValue]="item.id" [nzLabel]="item.buildingTypeName">
</nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
</form>
</div>

25
src/app/home/basic-info/add-unit/add-unit.component.spec.ts

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AddUnitComponent } from './add-unit.component';
describe('AddUnitComponent', () => {
let component: AddUnitComponent;
let fixture: ComponentFixture<AddUnitComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AddUnitComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AddUnitComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

86
src/app/home/basic-info/add-unit/add-unit.component.ts

@ -1,4 +1,4 @@
import { Component, OnInit,Input } from '@angular/core';
import { Component, OnInit, Input } from '@angular/core';
import { NzModalRef } from 'ng-zorro-antd/modal';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
@ -10,88 +10,26 @@ import { TreeService } from 'src/app/service/tree.service';
})
export class AddUnitComponent implements OnInit {
@Input() title?: string;
@Input() subtitle?: string;
@Input() nodes?: any;
@Input() BuildingTypes?: any;
validateForm!: FormGroup;
zhidui=[]
dadui=[]
jiuyuanzhan=[]
constructor(private modal: NzModalRef, private fb: FormBuilder, private http: HttpClient, private toTree: TreeService) { }
ngOnInit(): void {
this.validateForm = this.fb.group({
unit: [null, [Validators.required]],
name: [null],
iphone: [null],
unitname: [null, [Validators.required]],
person: [null],
phone: [null],
addr: [null],
role: [null],
role2: [null],
nature: [null],
role4: [null],
phonenum: [null]
orStation: [null, [Validators.required]],
orDa: [null],
nature: [null, [Validators.required]],
unittype: [null]
});
this.getAllOrganization()
}
destroyModal(): void {
this.modal.destroy({ data: 'this the result data' });
}
listOfData: any[] = [];
listOfData2: any[] = [];
//获取角色列表
getAllRoles() {
let params = {
SkipCount: '0',
MaxResultCount: '999'
}
this.http.get('/api/services/app/Role/GetAll', {
params: params
}).subscribe((data: any) => {
// console.log('角色列表', data.result.items)
this.listOfData = data.result.items
})
}
//获取角色列表
getAllRoles2() {
let params = {
SkipCount: '0',
MaxResultCount: '999',
IsViolationRoles:'true'
}
this.http.get('/api/services/app/Role/GetAll', {
params: params
}).subscribe((data: any) => {
// console.log('角色列表', data.result.items)
this.listOfData2 = data.result.items
})
}
//获取所有组织机构
nodes: any = []
getAllOrganization(){
let params = {
// OrganizationUnitId: OrganizationUnitId,
// IsContainsChildren: "true"
ContainsChildren: true,
pageSize: 9999
}
this.http.get('/api/Organizations', {
params: params
}).subscribe((data: any) => {
console.log(data);
for (let index = 0; index < data.items.length; index++) {
if(data.items[index].level==1){
this.zhidui.push(data.items[index])
}else if(data.items[index].level==2){
this.dadui.push(data.items[index])
}else{
this.jiuyuanzhan.push(data.items[index])
}
}
console.log(this.dadui);
})
}
}

63
src/app/home/basic-info/unit/unit.component.html

@ -3,33 +3,43 @@
<span>单位基本信息</span>
</div>
<div class="topbox">
<input type="text" nz-input placeholder="请输入单位" />
<nz-select nzAllowClear nzPlaceHolder="单位类型">
<input [(ngModel)]="searchValue.unit" type="text" nz-input placeholder="请输入单位" />
<nz-select [(ngModel)]="searchValue.uniytype" nzAllowClear nzPlaceHolder="单位类型">
<nz-option *ngFor="let item of BuildingTypes" [nzValue]="item.id" [nzLabel]="item.buildingTypeName"></nz-option>
</nz-select>
<input type="text" nz-input placeholder="请输入所属机构" />
<nz-select nzAllowClear nzPlaceHolder="使用性质">
<nz-tree-select [nzExpandedKeys]="expandKeys" [nzNodes]="nodes" nzShowSearch nzPlaceHolder="所属机构"
[(ngModel)]="searchValue.or" [nzExpandedIcon]="multiExpandedIconTpl" [nzDropdownClassName]="'maxHeightTreeSelect'"
[nzAllowClear]="false"></nz-tree-select>
<ng-template #multiExpandedIconTpl let-node let-origin="origin">
<ng-container *ngIf="node.children.length == 0; else elseTemplate">
</ng-container>
<ng-template #elseTemplate>
<i nz-icon [nzType]="node.isExpanded ? 'caret-down' : 'caret-right'" class="ant-tree-switcher-line-icon"></i>
</ng-template>
</ng-template>
<nz-select [(ngModel)]="searchValue.property" nzAllowClear nzPlaceHolder="使用性质">
<nz-option nzValue="一般单位" nzLabel="一般单位"></nz-option>
<nz-option nzValue="重点单位" nzLabel="重点单位"></nz-option>
</nz-select>
<button nz-button nzType="primary"><i nz-icon nzType="search" nzTheme="outline"></i>查询</button>
<button nz-button><i nz-icon nzType="reload" nzTheme="outline"></i>重置</button>
<button (click)="search()" nz-button nzType="primary"><i nz-icon nzType="search" nzTheme="outline"></i>查询</button>
<button (click)="reset()" nz-button><i nz-icon nzType="reload" nzTheme="outline"></i>重置</button>
</div>
<div class="translate">
<button nz-button nzType="primary" (click)="addOr()"><i nz-icon nzType="plus-circle"
<button nz-button nzType="primary" (click)="addUnit()"><i nz-icon nzType="plus-circle"
nzTheme="outline"></i>新增</button>
<!-- <button nz-button nzType="primary"><i nz-icon nzType="edit" nzTheme="outline"></i>修改</button>
<button nz-button nzType="primary" nzDanger><i nz-icon nzType="delete" nzTheme="outline"></i>删除</button> -->
</div>
<nz-table [nzLoading]="nzLoading" [nzBordered]="true" #basicTable [nzData]="listOfData">
<nz-table [nzLoading]="nzLoading" [nzBordered]="true" #basicTable [nzData]="listOfData" [nzShowPagination]='false'>
<thead>
<tr>
<th>单位名称</th>
<th>信息完整度</th>
<th nzWidth="11%">信息完整度</th>
<th>所属机构</th>
<th>单位级别</th>
<th>单位类型</th>
<th>使用性质</th>
<th>修改时间</th>
<th>创建时间</th>
<th>单位地址</th>
<!-- <th>状态</th> -->
<th>操作</th>
@ -38,18 +48,17 @@
<tbody>
<tr *ngFor="let data of basicTable.data">
<td>{{ data.companyName }}</td>
<td>
<div style="height: 20px; background: #eee; border-radius: 4px;">
<div [ngStyle]="{'width':integrity+'%'}"
style=" height: 100%; background: #46B783; border-radius: 4px; text-align: center; color: #fff;">
{{integrity}}%</div>
</div>
<td class="progresssquare">
<nz-progress [nzShowInfo]="false" nzStrokeLinecap="square" nzStrokeWidth="16" nzStrokeColor="#42B983" [nzPercent]="30">
</nz-progress>
<span class="num">30%</span>
</td>
<td>{{ data.organization }}</td>
<td>{{ data.level }}</td>
<td>{{ data.useNature }}</td>
<td>{{ data.creationTime | date:"yyyy-MM-dd hh:mm:ss"}}</td>
<td>{{ data.addr }}</td>
<td>{{ data.lastModificationTime ? data.lastModificationTime : data.creationTime | date:"yyyy-MM-dd hh:mm:ss"}}
</td>
<td>{{ data.address }}</td>
<!-- <td>{{ data.state }}</td> -->
<td class="operation">
<a class="bule" (click)="look(data)">查看</a>
@ -59,12 +68,12 @@
</td>
</tr>
</tbody>
<div class="pagination">
<nz-pagination [nzHideOnSinglePage]="false" [nzPageIndex]="1" [nzTotal]="totalCount" [nzPageSize]="16"
[nzShowTotal]="totalTemplate" nzShowQuickJumper (nzPageIndexChange)="pageChange($event)">
</nz-pagination>
<ng-template #totalTemplate let-total> 16条/页,共{{totalCount}}条 </ng-template>
</div>
</nz-table>
<div class="pagination">
<nz-pagination [nzHideOnSinglePage]="false" [nzPageIndex]="PageNumber" [nzTotal]="totalCount" [nzPageSize]="10"
[nzShowTotal]="totalTemplate" nzShowQuickJumper (nzPageIndexChange)="pageChange($event)">
</nz-pagination>
<ng-template #totalTemplate let-total> 10条/页,共{{totalCount}}条 </ng-template>
</div>
</div>

15
src/app/home/basic-info/unit/unit.component.scss

@ -27,7 +27,8 @@
margin-top: 20px;
input,
nz-select {
nz-select,
nz-tree-select {
width: 220px;
margin-right: 10px;
text-align: left;
@ -61,9 +62,21 @@
margin: 12px 0;
}
.progresssquare {
position: relative;
.num{
position: absolute;
left: 50%;
top: 54%;
transform: translate(-50%,-50%);
}
}
.nodebox {
font-size: 15px;
}
.operation {
a {
margin-right: 6px;

165
src/app/home/basic-info/unit/unit.component.ts

@ -15,83 +15,148 @@ import { UnitEditComponent } from '../unit-edit/unit-edit.component'
styleUrls: ['./unit.component.scss']
})
export class UnitComponent implements OnInit {
checked = true
listOfData: any = [];
integrity = 0
validateForm!: FormGroup;
constructor(private router: Router, private fb: FormBuilder, private http: HttpClient, private toTree: TreeService, private modal: NzModalService, private message: NzMessageService, private viewContainerRef: ViewContainerRef) { }
datas = ""
PageNumber = 1
PageSize = 10
ngOnInit(): void {
this.getCompanies()
this.getBuildingTypes()
this.getAllOrganization()
}
expandKeys
defaultOrId: string
//获取所有组织机构
nodes: any = []
getAllOrganization() {
let organizationId = JSON.parse(sessionStorage.getItem('userData')).organizationId
let params = {
OrganizationId: organizationId || '',
ContainsChildren: "true",
PageNumber: 1,
PageSize: 9999
}
this.http.get('/api/Organizations', {
params: params
}).subscribe((data: any) => {
data.items.forEach(element => {
if (element.id == organizationId) {
element.parentId = null
}
element.key = element.id
element.title = element.name
});
this.nodes = [...this.toTree.toTree(data.items)]
this.searchValue.or = JSON.parse(sessionStorage.getItem('userData')).organizationId
//回显
let unitPagesData = JSON.parse(sessionStorage.getItem('unitPagesData')) || null
if (unitPagesData) {
this.searchValue = unitPagesData.searchValue
this.PageNumber = unitPagesData.PageNumber
}
this.getCompanies()
})
}
searchValue = '';
next() {
this.router.navigate(['/basicInfo/unit/details']);
BuildingTypes
getBuildingTypes() {
this.http.get('/api/BuildingTypes').subscribe((data: any) => {
this.BuildingTypes = data
})
}
totalCount
pageChange($event) {
searchValue = {
unit: '',
uniytype: '',
or: JSON.parse(sessionStorage.getItem('userData')).organizationId,
property: ''
};
}
nzLoading = false
totalCount
getCompanies() {
this.nzLoading = true
this.http.get('/api/Companies').subscribe((data: any) => {
let params = {
CompanyName: this.searchValue.unit,
PageNumber: this.PageNumber,
PageSize: this.PageSize
}
this.http.get('/api/Companies', {
params: params
}).subscribe((data: any) => {
this.nzLoading = false
console.log(data);
this.totalCount = data.totalCount
this.listOfData = data.items
this.listOfData = [...this.listOfData]
console.log(this.listOfData);
})
}
pageChange($event) {
this.PageNumber = $event
this.getCompanies()
}
search() {
this.PageNumber = 1
this.getCompanies()
}
reset() {
this.PageNumber = 1
this.searchValue = {
unit: '',
uniytype: '',
or: JSON.parse(sessionStorage.getItem('userData')).organizationId,
property: ''
};
this.getCompanies()
}
ngOnDestroy(): void {
// CustomReuseStrategy.deleteRouteSnapshot('/basicInfo/unit');
}
addOr(node?: any) {
addUnit() {
const modal = this.modal.create({
nzTitle: "新增单位",
nzContent: AddUnitComponent,
nzViewContainerRef: this.viewContainerRef,
nzWidth: 450,
nzComponentParams: {},
nzComponentParams: {
nodes: this.nodes,
BuildingTypes: this.BuildingTypes
},
nzOnOk: async () => {
console.log(instance.validateForm);
if (instance.validateForm.valid) {
console.log(instance.validateForm);
// return
await new Promise(resolve => {
await new Promise((resolve, reject) => {
let body = {
companyName: instance.validateForm.value.unit,
directorName: instance.validateForm.value.name,
directorPhone: instance.validateForm.value.iphone,
companyName: instance.validateForm.value.unitname,
directorName: instance.validateForm.value.person,
directorPhone: instance.validateForm.value.phone,
address: instance.validateForm.value.addr,
organizationId: instance.validateForm.value.role || null,
relatedOrganizationId: instance.validateForm.value.role2 || null,
buildingTypeId: instance.validateForm.value.role4 || null,
organizationId: instance.validateForm.value.orStation || null,
relatedOrganizationId: instance.validateForm.value.orDa || null,
buildingTypeId: instance.validateForm.value.unittype || null,
useNature: instance.validateForm.value.nature,
data: null
}
this.http.post('/api/Companies', body).subscribe({
next: (data: any) => {
console.log(data, 80808)
next: async (data) => {
this.message.create('success', '创建成功');
resolve(data)
await this.getCompanies()
return true
},
error: (err) => {
console.log(err)
this.message.create('warning', '创建失败');
reject(err)
return false
}
}
)
})
})
} else {
@ -106,14 +171,34 @@ export class UnitComponent implements OnInit {
look(data) {
let unitPagesData = {
searchValue: this.searchValue,
PageNumber: this.PageNumber
}
sessionStorage.setItem('unitPagesData', JSON.stringify(unitPagesData))
this.router.navigate(['/basicInfo/unit/details'], { queryParams: { id: data.id, pattern: 'look' } })
}
edit(data) {
let unitPagesData = {
searchValue: this.searchValue,
PageNumber: this.PageNumber
}
sessionStorage.setItem('unitPagesData', JSON.stringify(unitPagesData))
this.router.navigate(['/basicInfo/unit/details'], { queryParams: { id: data.id, pattern: 'edit' } })
}
delete(data) {
delete(item) {
this.modal.confirm({
nzTitle: `确定要删除${item.companyName}这个单位吗?`,
nzOkText: '确定',
nzOkType: 'default',
nzOnOk: () => {
this.http.delete(`/api/Users/${item.id}`).subscribe(data => {
this.message.create('success', '删除成功!');
this.getCompanies()
})
},
nzCancelText: '取消'
});
}
}

6
src/app/home/task/da-oneself-plan/da-oneself-plan.component.html

@ -82,8 +82,8 @@
<span style="width: 8%;">单位总数: 7/8</span>
<div style="width: 66%;" class="progress progresssquare">
<span>完成进度</span>
<nz-progress nzStrokeLinecap="square" nzStrokeWidth="16" nzStrokeColor="#42B983"
[nzPercent]="30"></nz-progress>
<nz-progress nzStrokeLinecap="square" nzStrokeWidth="16"
nzStrokeColor="#42B983" [nzPercent]="30"></nz-progress>
</div>
<button nz-button nzType="primary">任务下派</button>
<span (click)="expandcarditem(item)" class="expand blue">
@ -224,7 +224,7 @@
<button style="margin-left: 12px;" nz-button nzType="primary">取消</button>
</div>
</div>
</div>
</div>
</div>

Loading…
Cancel
Save