Browse Source

feat(inspection): 新增待办列表并优化排程界面

- 新增 myTodoList.vue 页面实现 QC 人员待办功能
- 在 inspectionScheduleView.vue 中添加无数据提示状态
- 重构排程表单的 QC 人员选择逻辑,区分显示名称和提交用户名
- 添加路由参数监听功能,支持从待办页面跳转到申请单列表
- 优化多个检验模块的搜索表单项布局和样式
- 在申请单列表中实现 Excel 导出功能
- 移除主表格的红色背景样式标记
- 添加定时刷新机制确保待办数据实时更新
master
qiankanghui 14 hours ago
parent
commit
e7711f4267
  1. 76
      src/views/modules/inspection/com_inspectionScheduleView.vue
  2. 106
      src/views/modules/inspection/inspectionPendingList.vue
  3. 4
      src/views/modules/inspection/inspectionRequestAudit.vue
  4. 46
      src/views/modules/inspection/inspectionRequestList.vue
  5. 4
      src/views/modules/inspection/inspectionScheduleList.vue
  6. 336
      src/views/modules/inspection/myTodoList.vue

76
src/views/modules/inspection/com_inspectionScheduleView.vue

@ -1,5 +1,13 @@
<template> <template>
<div class="schedule-container"> <div class="schedule-container">
<!-- 无数据提示 -->
<div v-if="!hasSelectedData" class="empty-state">
<i class="el-icon-info" style="font-size: 48px; color: #c0c4cc;"></i>
<p style="color: #909399; margin-top: 16px;">请先在主表中选择需要排程的申请单</p>
</div>
<!-- 有数据时显示正常内容 -->
<template v-else>
<!-- 左侧QC人员清单 --> <!-- 左侧QC人员清单 -->
<div class="left-panel"> <div class="left-panel">
<div class="panel-title">QC人员清单</div> <div class="panel-title">QC人员清单</div>
@ -62,6 +70,7 @@
</div> </div>
</div> </div>
</div> </div>
</template>
<!-- 排程弹窗 --> <!-- 排程弹窗 -->
<el-dialog <el-dialog
@ -74,20 +83,14 @@
:model="scheduleForm" :model="scheduleForm"
:rules="scheduleRules" :rules="scheduleRules"
ref="scheduleForm" ref="scheduleForm"
label-width="110px"
size="small">
<el-form-item label="QC人员:" prop="qcOperator">
<el-select
size="small"
label-position="top">
<el-form-item label="QC人员:">
<el-input
v-model="scheduleForm.qcOperator" v-model="scheduleForm.qcOperator"
placeholder="请选择QC人员"
style="width: 100%;">
<el-option
v-for="qc in qcList"
:key="qc.id"
:label="qc.qcName"
:value="qc.qcName">
</el-option>
</el-select>
disabled
style="width: 100px;">
</el-input>
</el-form-item> </el-form-item>
<el-form-item label="计划验货日期:" prop="planStartDate"> <el-form-item label="计划验货日期:" prop="planStartDate">
@ -166,15 +169,13 @@ export default {
// //
scheduleDialogVisible: false, scheduleDialogVisible: false,
scheduleForm: { scheduleForm: {
qcOperator: '',
qcOperator: '', //
qcUserName: '', //
planStartDate: '', planStartDate: '',
planEndDate: '', planEndDate: '',
remark: '' remark: ''
}, },
scheduleRules: { scheduleRules: {
qcOperator: [
{ required: true, message: '请选择QC人员', trigger: 'change' }
],
planStartDate: [ planStartDate: [
{ required: true, message: '请选择开始日期', trigger: 'change' } { required: true, message: '请选择开始日期', trigger: 'change' }
] ]
@ -182,6 +183,30 @@ export default {
} }
}, },
computed: {
//
hasSelectedData () {
return this.requestNos && this.requestNos.length > 0
}
},
watch: {
//
requestNos: {
handler (newVal) {
if (newVal && newVal.length > 0) {
// QC
this.loadQcList()
} else {
//
this.selectedQc = null
this.scheduleDays = []
}
},
immediate: true
}
},
methods: { methods: {
// QC // QC
loadQcList () { loadQcList () {
@ -228,14 +253,17 @@ export default {
requestNos = this.requestNos || [] requestNos = this.requestNos || []
// QC // QC
this.scheduleForm.qcOperator = qc.qcName || qc.userName this.scheduleForm.qcOperator = qc.qcName || qc.userName
this.scheduleForm.qcUserName = qc.userName
} else if (task && task.requestNo) { } else if (task && task.requestNo) {
// //
requestNos = [task.requestNo] requestNos = [task.requestNo]
this.scheduleForm.qcOperator = this.selectedQc ? this.selectedQc.qcName : '' this.scheduleForm.qcOperator = this.selectedQc ? this.selectedQc.qcName : ''
this.scheduleForm.qcUserName = this.selectedQc ? this.selectedQc.userName : ''
} else { } else {
// //
requestNos = this.requestNos || [] requestNos = this.requestNos || []
this.scheduleForm.qcOperator = this.selectedQc ? this.selectedQc.qcName : '' this.scheduleForm.qcOperator = this.selectedQc ? this.selectedQc.qcName : ''
this.scheduleForm.qcUserName = this.selectedQc ? this.selectedQc.userName : ''
} }
if (requestNos.length === 0) { if (requestNos.length === 0) {
@ -264,7 +292,7 @@ export default {
}).then(() => { }).then(() => {
const params = { const params = {
requestNos: this.scheduleForm.requestNos, requestNos: this.scheduleForm.requestNos,
qcOperator: this.scheduleForm.qcOperator,
qcOperator: this.scheduleForm.qcUserName, // 使
planStartDate: this.scheduleForm.planStartDate, planStartDate: this.scheduleForm.planStartDate,
planEndDate: this.scheduleForm.planEndDate || this.scheduleForm.planStartDate, planEndDate: this.scheduleForm.planEndDate || this.scheduleForm.planStartDate,
remark: this.scheduleForm.remark remark: this.scheduleForm.remark
@ -396,6 +424,18 @@ export default {
display: flex; display: flex;
height: 100%; height: 100%;
background: #fff; background: #fff;
position: relative;
}
//
.empty-state {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #fafafa;
} }
// //

106
src/views/modules/inspection/inspectionPendingList.vue

@ -9,7 +9,7 @@
<el-input v-model="searchData.supplierNo" class="input-small" @keyup.enter.native="getMainData"/> <el-input v-model="searchData.supplierNo" class="input-small" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="供应商名称"> <el-form-item label="供应商名称">
<el-input v-model="searchData.supplierName" class="input-small" @keyup.enter.native="getMainData"/>
<el-input v-model="searchData.supplierName" class="input-small" style="width: 200px;" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="申请日期"> <el-form-item label="申请日期">
@ -34,8 +34,8 @@
<el-input v-model="searchData.createBy" class="input-small" @keyup.enter.native="getMainData"/> <el-input v-model="searchData.createBy" class="input-small" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="QC人员">
<el-input v-model="searchData.qcOperator" disabled class="input-small"/>
<el-form-item label="申请单号">
<el-input v-model="searchData.requestNo" class="input-small" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="建议验货日期"> <el-form-item label="建议验货日期">
@ -45,13 +45,6 @@
<el-date-picker v-model="searchData.needInspectDateEnd" type="date" value-format="yyyy-MM-dd" placeholder="结束" class="date-small"/> <el-date-picker v-model="searchData.needInspectDateEnd" type="date" value-format="yyyy-MM-dd" placeholder="结束" class="date-small"/>
</div> </div>
</el-form-item> </el-form-item>
</div>
<!-- 第三行 -->
<div class="search-row">
<el-form-item label="申请单号">
<el-input v-model="searchData.requestNo" class="input-small" @keyup.enter.native="getMainData"/>
</el-form-item>
<el-form-item label="计划验货日期"> <el-form-item label="计划验货日期">
<div class="date-range"> <div class="date-range">
@ -78,7 +71,6 @@
highlight-current-row highlight-current-row
@row-click="changeData" @row-click="changeData"
v-loading="dataListLoading" v-loading="dataListLoading"
:row-class-name="mainTableRowClassName"
style="margin-top: 0px; width: 100%;"> style="margin-top: 0px; width: 100%;">
<el-table-column <el-table-column
v-for="(item,index) in columnArray1" :key="index" v-for="(item,index) in columnArray1" :key="index"
@ -408,10 +400,61 @@ export default {
] ]
} }
}, },
watch: {
//
'$route.query': {
handler (newQuery, oldQuery) {
//
const hasChanges = newQuery.planStartDate !== (oldQuery && oldQuery.planStartDate) ||
newQuery.planEndDate !== (oldQuery && oldQuery.planEndDate) ||
newQuery.requestNo !== (oldQuery && oldQuery.requestNo) ||
newQuery.supplierName !== (oldQuery && oldQuery.supplierName)
if (hasChanges && (newQuery.planStartDate || newQuery.requestNo || newQuery.supplierName)) {
//
if (newQuery.planStartDate && newQuery.planEndDate) {
this.searchData.planStartDate = newQuery.planStartDate
this.searchData.planEndDate = newQuery.planEndDate
}
//
if (newQuery.requestNo) {
this.searchData.requestNo = newQuery.requestNo
}
//
if (newQuery.supplierName) {
this.searchData.supplierName = newQuery.supplierName
}
this.getMainData()
}
},
immediate: false
}
},
mounted () { mounted () {
this.$nextTick(() => { this.$nextTick(() => {
this.height = (window.innerHeight - 220) / 2 this.height = (window.innerHeight - 220) / 2
this.detailHeight = (window.innerHeight - 220) / 2 this.detailHeight = (window.innerHeight - 220) / 2
//
const query = this.$route.query
//
if (query.planStartDate && query.planEndDate) {
this.searchData.planStartDate = query.planStartDate
this.searchData.planEndDate = query.planEndDate
}
//
if (query.requestNo) {
this.searchData.requestNo = query.requestNo
}
//
if (query.supplierName) {
this.searchData.supplierName = query.supplierName
}
//
this.getMainData() this.getMainData()
}) })
}, },
@ -539,23 +582,10 @@ export default {
viewDetail (row) { viewDetail (row) {
this.changeData(row) this.changeData(row)
// //
},
//
mainTableRowClassName ({ row }) {
if (row.hasModifiedQty === true ||
row.hasModifiedQty === 'true' ||
row.hasModifiedQty === 'Y' ||
row.hasModifiedQty === 'y' ||
row.hasModifiedQty === 1 ||
row.hasModifiedQty === '1') {
return 'modified-request-row'
}
return ''
} }
}, },
created () { created () {
this.getMainData()
// created mounted
} }
} }
</script> </script>
@ -643,30 +673,4 @@ export default {
/deep/ .customer-tab .el-tabs__content { /deep/ .customer-tab .el-tabs__content {
padding: 5px !important; padding: 5px !important;
} }
/* 强制主表红色背景 */
::v-deep .el-table__body tr.modified-request-row td {
background-color: #ff4d4f !important;
color: #ffffff !important;
}
/* 当前选中行也强制红色 */
::v-deep .el-table__body tr.modified-request-row.current-row td {
background-color: #ff4d4f !important;
color: #ffffff !important;
}
/* hover状态 */
::v-deep .el-table__body tr.modified-request-row:hover td {
background-color: #ff7875 !important;
color: #ffffff !important;
}
</style>
<!-- 全局样式 - 仅对有 modified-request-row 类的行生效 -->
<style lang="scss">
.el-table__body tr.modified-request-row > td {
background-color: #ffe6e6 !important;
color: #333333 !important;
}
</style> </style>

4
src/views/modules/inspection/inspectionRequestAudit.vue

@ -9,7 +9,7 @@
<el-input v-model="searchData.supplierNo" class="input-small" @keyup.enter.native="getMainData"/> <el-input v-model="searchData.supplierNo" class="input-small" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="供应商名称"> <el-form-item label="供应商名称">
<el-input v-model="searchData.supplierName" class="input-small" @keyup.enter.native="getMainData"/>
<el-input v-model="searchData.supplierName" class="input-small" style="width: 200px;" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="申请日期"> <el-form-item label="申请日期">
@ -39,7 +39,7 @@
<el-input v-model="searchData.createBy" class="input-small" @keyup.enter.native="getMainData"/> <el-input v-model="searchData.createBy" class="input-small" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="建议验货日期">
<el-form-item label="建议验货日期" style="margin-left: 90px;">
<div class="date-range"> <div class="date-range">
<el-date-picker v-model="searchData.needInspectDateStart" type="date" value-format="yyyy-MM-dd" placeholder="开始" class="date-small"/> <el-date-picker v-model="searchData.needInspectDateStart" type="date" value-format="yyyy-MM-dd" placeholder="开始" class="date-small"/>
<span class="split">-</span> <span class="split">-</span>

46
src/views/modules/inspection/inspectionRequestList.vue

@ -9,7 +9,7 @@
<el-input v-model="searchData.supplierNo" class="input-small" @keyup.enter.native="getMainData"/> <el-input v-model="searchData.supplierNo" class="input-small" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="供应商名称"> <el-form-item label="供应商名称">
<el-input v-model="searchData.supplierName" class="input-small" @keyup.enter.native="getMainData"/>
<el-input v-model="searchData.supplierName" class="input-small" style="width: 200px;" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="申请日期"> <el-form-item label="申请日期">
@ -44,7 +44,7 @@
<el-form-item> <el-form-item>
<span style="cursor: pointer" slot="label" @click="getBaseList(2016, 'qcOperator')"><a href="#">QC人员</a></span> <span style="cursor: pointer" slot="label" @click="getBaseList(2016, 'qcOperator')"><a href="#">QC人员</a></span>
<el-input v-model="searchData.qcOperator" class="input-small" @keyup.enter.native="getMainData"/>
<el-input v-model="searchData.qcOperator" class="input-small" style="width: 200px;" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="建议验货日期" > <el-form-item label="建议验货日期" >
@ -362,6 +362,7 @@ import ComInspectionRequestDetailTab from './com_inspectionRequestDetailTab.vue'
import ComInspectionRequestPoDetailTab from './com_inspectionRequestPoDetailTab.vue' import ComInspectionRequestPoDetailTab from './com_inspectionRequestPoDetailTab.vue'
import ComInspectionResultTab from './com_inspectionResultTab.vue' import ComInspectionResultTab from './com_inspectionResultTab.vue'
import ComInspectionRequestAttachmentTab from './com_inspectionRequestAttachmentTab.vue' import ComInspectionRequestAttachmentTab from './com_inspectionRequestAttachmentTab.vue'
import excel from '@/utils/excel-util.js'
export default { export default {
components: { components: {
@ -1087,7 +1088,46 @@ export default {
// //
exportExcel () { exportExcel () {
this.$message.info('导出功能开发中...')
console.log('开始导出...')
// 使 Date
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
const hour = String(now.getHours()).padStart(2, '0')
const minute = String(now.getMinutes()).padStart(2, '0')
const second = String(now.getSeconds()).padStart(2, '0')
const exportName = `检验申请单${year}${month}${day}${hour}${minute}${second}`
console.log('导出参数:', this.searchData)
//
const exportParams = {
...this.searchData,
limit: -1, //
page: 1
}
try {
excel.exportTable({
url: '/inspection/searchInspectionRequestHeaderList',
columnMapping: this.columnArray1, // 使
mergeSetting: [], //
params: exportParams,
fileName: exportName + '.xlsx',
rowFetcher: res => {
console.log('导出数据响应:', res)
return res.data.page.list
}, //
columnFormatter: [], //
dropColumns: [] //
})
console.log('导出请求已发送')
} catch (error) {
console.error('导出失败:', error)
this.$message.error('导出失败: ' + error.message)
}
}, },
// //

4
src/views/modules/inspection/inspectionScheduleList.vue

@ -9,7 +9,7 @@
<el-input v-model="searchData.supplierNo" class="input-small" @keyup.enter.native="getMainData"/> <el-input v-model="searchData.supplierNo" class="input-small" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="供应商名称"> <el-form-item label="供应商名称">
<el-input v-model="searchData.supplierName" class="input-small" @keyup.enter.native="getMainData"/>
<el-input v-model="searchData.supplierName" class="input-small" style="width: 200px;" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="申请日期"> <el-form-item label="申请日期">
@ -37,7 +37,7 @@
<el-form-item> <el-form-item>
<span style="cursor: pointer" slot="label" @click="getBaseList(2016, 'qcOperator')"><a href="#">QC人员</a></span> <span style="cursor: pointer" slot="label" @click="getBaseList(2016, 'qcOperator')"><a href="#">QC人员</a></span>
<el-input v-model="searchData.qcOperator" class="input-small" @keyup.enter.native="getMainData"/>
<el-input v-model="searchData.qcOperator" class="input-small" style="width: 200px;" @keyup.enter.native="getMainData"/>
</el-form-item> </el-form-item>
<el-form-item label="建议验货日期" > <el-form-item label="建议验货日期" >

336
src/views/modules/inspection/myTodoList.vue

@ -0,0 +1,336 @@
<template>
<div class="my-todo-container">
<!-- 页面标题 -->
<div class="page-header">
<h2 class="page-title">我的代办QC人员</h2>
<p class="page-desc">对于QC人员查看到自己的待验货的申请单</p>
</div>
<!-- 日期卡片网格 -->
<div class="schedule-grid" v-loading="loading">
<div
v-for="day in scheduleDays"
:key="day.date"
class="schedule-card">
<div class="card-header">
<span class="date-label">{{ day.dateLabel }}</span>
<span class="date-weekday">{{ day.weekday }}</span>
</div>
<div class="card-body">
<div
v-for="task in day.tasks"
:key="task.requestNo"
class="task-item"
@click="handleTaskClick(task, day.date)">
<div class="task-no">单号{{ task.requestNo }}</div>
<div class="task-supplier" :title="task.supplierName">{{ task.supplierName }}</div>
<div v-if="task.inspectAddress" class="task-address" :title="task.inspectAddress">
<i class="el-icon-location"></i> {{ task.inspectAddress }}
</div>
</div>
<div v-if="day.tasks.length === 0" class="empty-slot">
<span class="empty-text">暂无排程</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { getScheduleView } from '@/api/inspection/inspectionRequestHeader.js'
export default {
name: 'MyTodoList',
data () {
return {
scheduleDays: [], // 10
loading: false, //
refreshTimer: null //
}
},
mounted () {
this.loadScheduleData()
//
this.refreshTimer = setInterval(() => {
this.loadScheduleData()
}, 60000)
},
beforeDestroy () {
//
if (this.refreshTimer) {
clearInterval(this.refreshTimer)
this.refreshTimer = null
}
},
methods: {
//
loadScheduleData () {
this.loading = true
// 使QC
const qcOperator = this.$store.state.user.name
getScheduleView(qcOperator).then(({ data }) => {
if (data.code === 0 && data.list) {
// 10
const days = this.generateNextDays(10)
//
const tasksByDate = {}
data.list.forEach(task => {
const dateStr = this.formatDate(new Date(task.planStartDate))
if (!tasksByDate[dateStr]) {
tasksByDate[dateStr] = []
}
tasksByDate[dateStr].push({
requestNo: task.requestNo,
supplierName: task.supplierName,
inspectAddress: task.inspectAddress,
planStartDate: task.planStartDate,
planEndDate: task.planEndDate
})
})
//
this.scheduleDays = days.map(day => ({
...day,
tasks: tasksByDate[day.date] || []
}))
console.log('加载排程数据成功:', this.scheduleDays)
} else {
// 使
this.scheduleDays = this.generateNextDays(10)
}
}).catch((error) => {
console.error('加载排程数据错误:', error)
// 使
this.scheduleDays = this.generateNextDays(10)
}).finally(() => {
this.loading = false
})
},
// N
generateNextDays (days) {
const result = []
const today = new Date()
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
for (let i = 0; i < days; i++) {
const date = new Date(today)
date.setDate(today.getDate() + i)
const dateStr = this.formatDate(date)
const dateLabel = `${date.getMonth() + 1}/${date.getDate()}`
const weekday = weekdays[date.getDay()]
result.push({
date: dateStr,
dateLabel: dateLabel,
weekday: weekday,
tasks: []
})
}
return result
},
// yyyy-MM-dd
formatDate (date) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
//
handleTaskClick (task, date) {
console.log('点击任务:', task, '日期:', date)
//
const query = {
planStartDate: date,
planEndDate: date
}
//
if (task.requestNo) {
query.requestNo = task.requestNo
}
//
if (task.supplierName) {
query.supplierName = task.supplierName
}
this.$router.push({
path: '/inspection-inspectionPendingList',
query: query
})
}
}
}
</script>
<style scoped lang="scss">
.my-todo-container {
padding: 20px;
background: #fff;
min-height: 100%;
}
//
.page-header {
margin-bottom: 20px;
.page-title {
font-size: 20px;
font-weight: bold;
color: #303133;
margin: 0 0 8px 0;
}
.page-desc {
font-size: 14px;
color: #909399;
margin: 0;
}
}
//
.schedule-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(2, 1fr);
gap: 15px;
min-height: 600px;
}
//
.schedule-card {
border: 2px solid #dcdfe6;
border-radius: 4px;
background: #fff;
display: flex;
flex-direction: column;
min-height: 180px;
transition: all 0.3s;
&:hover {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.card-header {
padding: 10px 12px;
background: #f5f7fa;
border-bottom: 2px solid #dcdfe6;
display: flex;
justify-content: space-between;
align-items: center;
.date-label {
font-size: 18px;
font-weight: bold;
color: #303133;
}
.date-weekday {
font-size: 13px;
color: #909399;
}
}
.card-body {
flex: 1;
padding: 10px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 8px;
}
}
//
.task-item {
padding: 10px;
background: #f5f7fa;
border: 1px solid #dcdfe6;
border-radius: 3px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: #ecf5ff;
border-color: #409eff;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
transform: translateY(-2px);
}
.task-no {
font-size: 13px;
font-weight: bold;
color: #409eff;
margin-bottom: 5px;
}
.task-supplier {
font-size: 12px;
color: #606266;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 4px;
}
.task-address {
font-size: 11px;
color: #909399;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
i {
margin-right: 3px;
}
}
}
//
.empty-slot {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
min-height: 80px;
.empty-text {
color: #c0c4cc;
font-size: 13px;
}
}
//
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-thumb {
background: #c0c4cc;
border-radius: 3px;
&:hover {
background: #909399;
}
}
::-webkit-scrollbar-track {
background: #f5f7fa;
}
</style>
Loading…
Cancel
Save