|
|
|
@ -2,46 +2,43 @@ |
|
|
|
<div class="year-view"> |
|
|
|
<div class="year-header"> |
|
|
|
<h2>{{ currentYear }}年</h2> |
|
|
|
<div class="year-controls"> |
|
|
|
<el-date-picker |
|
|
|
v-model="selectedYear" |
|
|
|
style="float: right; width: 120px" |
|
|
|
type="year" |
|
|
|
size="mini" |
|
|
|
placeholder="选择年" |
|
|
|
@change="onYearChange" |
|
|
|
> |
|
|
|
</el-date-picker> |
|
|
|
</div> |
|
|
|
<el-date-picker |
|
|
|
v-model="selectedYear" |
|
|
|
class="year-picker" |
|
|
|
type="year" |
|
|
|
size="mini" |
|
|
|
placeholder="选择年份" |
|
|
|
@change="onYearChange" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="months-grid"> |
|
|
|
<div class="month-card" v-for="month in 12" :key="month"> |
|
|
|
<div v-loading="loading" class="months-grid"> |
|
|
|
<div v-for="month in 12" :key="month" class="month-card"> |
|
|
|
<div class="month-title">{{ getChineseMonth(month) }}</div> |
|
|
|
<!-- <div class="month-stats" v-if="hasData(month)"> --> |
|
|
|
<div class="month-stats"> |
|
|
|
<div class="stat-item"> |
|
|
|
正在执行: |
|
|
|
<span class="status-executing">{{ |
|
|
|
getMonthData(month).executing |
|
|
|
}}</span> |
|
|
|
</div> |
|
|
|
<div class="stat-item"> |
|
|
|
已经完成: |
|
|
|
<span class="status-completed">{{ |
|
|
|
getMonthData(month).executed |
|
|
|
}}</span> |
|
|
|
</div> |
|
|
|
<div class="stat-item"> |
|
|
|
未 执 行 : |
|
|
|
<span class="status-not-executed">{{ |
|
|
|
getMonthData(month).pending |
|
|
|
}}</span> |
|
|
|
<div class="month-body"> |
|
|
|
<div class="month-stats"> |
|
|
|
<div class="stat-item executing"> |
|
|
|
<i class="status-dot" /> |
|
|
|
<span class="stat-label">正在执行:</span> |
|
|
|
<strong>{{ getMonthData(month).executing }}</strong> |
|
|
|
</div> |
|
|
|
<div class="stat-item completed"> |
|
|
|
<i class="status-dot" /> |
|
|
|
<span class="stat-label">已经完成:</span> |
|
|
|
<strong>{{ getMonthData(month).executed }}</strong> |
|
|
|
</div> |
|
|
|
<div class="stat-item pending"> |
|
|
|
<i class="status-dot" /> |
|
|
|
<span class="stat-label">未执行:</span> |
|
|
|
<strong>{{ getMonthData(month).pending }}</strong> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<img |
|
|
|
class="month-icon" |
|
|
|
:src="yearTaskIcon" |
|
|
|
alt="" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
<!-- <div class="month-stats empty" v-else> |
|
|
|
<div class="stat-item">无数据</div> |
|
|
|
</div> --> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
@ -49,265 +46,90 @@ |
|
|
|
|
|
|
|
<script> |
|
|
|
import { calenderYear } from "@/api/videosMonitor/index"; |
|
|
|
import yearTaskIcon from "@/views/cards/assets/year-task.png"; |
|
|
|
|
|
|
|
export default { |
|
|
|
name: "YearView", |
|
|
|
props: { |
|
|
|
year: { |
|
|
|
type: Number, |
|
|
|
required: true, |
|
|
|
}, |
|
|
|
}, |
|
|
|
props: { year: { type: Number, required: true } }, |
|
|
|
data() { |
|
|
|
// 设置默认年份为当前年份 |
|
|
|
const currentYear = new Date().getFullYear(); |
|
|
|
return { |
|
|
|
selectedYear: new Date(currentYear, 0, 1), |
|
|
|
// 使用本地变量存储当前年份 |
|
|
|
currentYear: currentYear, |
|
|
|
chineseMonths: [ |
|
|
|
"一月", |
|
|
|
"二月", |
|
|
|
"三月", |
|
|
|
"四月", |
|
|
|
"五月", |
|
|
|
"六月", |
|
|
|
"七月", |
|
|
|
"八月", |
|
|
|
"九月", |
|
|
|
"十月", |
|
|
|
"十一月", |
|
|
|
"十二月", |
|
|
|
], |
|
|
|
// 移除假数据,初始化空对象 |
|
|
|
currentYear, |
|
|
|
chineseMonths: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"], |
|
|
|
yearDataMap: {}, |
|
|
|
loading: false, |
|
|
|
yearTaskIcon, |
|
|
|
}; |
|
|
|
}, |
|
|
|
watch: { |
|
|
|
// 监听 prop 的变化,更新内部数据 |
|
|
|
year(newYear) { |
|
|
|
this.currentYear = newYear; |
|
|
|
this.selectedYear = new Date(newYear, 0, 1); |
|
|
|
// 年份变化时重新获取数据 |
|
|
|
this.getYearData(); |
|
|
|
}, |
|
|
|
}, |
|
|
|
mounted() { |
|
|
|
// 组件挂载时获取数据 |
|
|
|
this.getYearData(); |
|
|
|
}, |
|
|
|
mounted() { this.getYearData(); }, |
|
|
|
methods: { |
|
|
|
onYearChange() { |
|
|
|
if (this.selectedYear) { |
|
|
|
this.currentYear = this.selectedYear.getFullYear(); |
|
|
|
// 通知父组件年份已更改 |
|
|
|
this.$emit("year-changed", this.currentYear); |
|
|
|
// 获取新年份的数据 |
|
|
|
this.getYearData(); |
|
|
|
} |
|
|
|
if (!this.selectedYear) return; |
|
|
|
this.currentYear = this.selectedYear.getFullYear(); |
|
|
|
this.$emit("year-changed", this.currentYear); |
|
|
|
this.getYearData(); |
|
|
|
}, |
|
|
|
// 获取年份数据 |
|
|
|
async getYearData() { |
|
|
|
this.loading = true; |
|
|
|
try { |
|
|
|
this.loading = true; |
|
|
|
const response = await calenderYear({ |
|
|
|
year: this.currentYear, |
|
|
|
}); |
|
|
|
|
|
|
|
// 确保响应数据存在 |
|
|
|
const response = await calenderYear({ year: this.currentYear }); |
|
|
|
const data = response?.rows || response || []; |
|
|
|
|
|
|
|
// 将接口数据转换为需要的格式 |
|
|
|
const monthData = {}; |
|
|
|
data.forEach(item => { |
|
|
|
monthData[item.month] = { |
|
|
|
executed: item.executed || 0, |
|
|
|
executing: item.executing || 0, |
|
|
|
pending: item.pending || 0, |
|
|
|
total: item.total || 0 |
|
|
|
const month = Number(item.month); |
|
|
|
if (!month) return; |
|
|
|
monthData[month] = { |
|
|
|
executed: Number(item.executed) || 0, |
|
|
|
executing: Number(item.executing) || 0, |
|
|
|
pending: Number(item.pending) || 0, |
|
|
|
total: Number(item.total) || 0, |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
// 更新数据映射 |
|
|
|
this.yearDataMap = { |
|
|
|
[this.currentYear]: monthData |
|
|
|
}; |
|
|
|
|
|
|
|
this.yearDataMap = { [this.currentYear]: monthData }; |
|
|
|
} catch (error) { |
|
|
|
console.error('获取年份数据失败:', error); |
|
|
|
// 出错时设置为空数据 |
|
|
|
this.yearDataMap = { |
|
|
|
[this.currentYear]: {} |
|
|
|
}; |
|
|
|
} finally { |
|
|
|
this.loading = false; |
|
|
|
} |
|
|
|
console.error("获取年份数据失败:", error); |
|
|
|
this.yearDataMap = { [this.currentYear]: {} }; |
|
|
|
} finally { this.loading = false; } |
|
|
|
}, |
|
|
|
getMonthData(month) { |
|
|
|
// 修复:确保总是返回一个包含默认值的对象,而不是null |
|
|
|
const yearData = this.yearDataMap[this.currentYear]; |
|
|
|
|
|
|
|
// 如果年份数据不存在,返回默认值 |
|
|
|
if (!yearData) { |
|
|
|
return { |
|
|
|
executed: 0, |
|
|
|
executing: 0, |
|
|
|
pending: 0, |
|
|
|
total: 0 |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
// 如果月份数据不存在,返回默认值 |
|
|
|
const monthStats = yearData[month]; |
|
|
|
if (!monthStats) { |
|
|
|
return { |
|
|
|
executed: 0, |
|
|
|
executing: 0, |
|
|
|
pending: 0, |
|
|
|
total: 0 |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
// 返回实际数据 |
|
|
|
return monthStats; |
|
|
|
return (this.yearDataMap[this.currentYear] || {})[month] || { executed: 0, executing: 0, pending: 0, total: 0 }; |
|
|
|
}, |
|
|
|
hasData(month) { |
|
|
|
const data = this.getMonthData(month); |
|
|
|
return data.executed > 0 || data.executing > 0 || data.pending > 0 || data.total > 0; |
|
|
|
}, |
|
|
|
getChineseMonth(monthNumber) { |
|
|
|
return this.chineseMonths[monthNumber - 1] || monthNumber + "月"; |
|
|
|
}, |
|
|
|
getChineseMonth(month) { return this.chineseMonths[month - 1]; }, |
|
|
|
}, |
|
|
|
}; |
|
|
|
</script> |
|
|
|
|
|
|
|
<style scoped> |
|
|
|
.year-view { |
|
|
|
width: 100%; |
|
|
|
min-height: 100%; |
|
|
|
color: #e8f2ff; |
|
|
|
} |
|
|
|
.year-header { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
margin-bottom: 20px; |
|
|
|
} |
|
|
|
|
|
|
|
.year-header h2 { |
|
|
|
margin: 0; |
|
|
|
font-size: 20px; |
|
|
|
color: #b9d6ff; |
|
|
|
} |
|
|
|
|
|
|
|
.year-controls { |
|
|
|
display: flex; |
|
|
|
gap: 10px; |
|
|
|
align-items: center; |
|
|
|
} |
|
|
|
|
|
|
|
.months-grid { |
|
|
|
display: grid; |
|
|
|
grid-template-columns: repeat(4, 1fr); |
|
|
|
gap: 15px; |
|
|
|
} |
|
|
|
|
|
|
|
.month-card { |
|
|
|
background: linear-gradient(135deg, rgba(18, 37, 105, 0.96), rgba(15, 31, 92, 0.96)); |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
min-height: 220px; |
|
|
|
padding: 24px 15px 28px; |
|
|
|
border-radius: 0; |
|
|
|
border: 1px solid #2787ed; |
|
|
|
box-shadow: inset 0 0 18px rgba(23, 91, 196, 0.12); |
|
|
|
transition: border-color 0.2s ease, box-shadow 0.2s ease; |
|
|
|
} |
|
|
|
|
|
|
|
.month-card:hover { |
|
|
|
border-color: #4fb5ff; |
|
|
|
box-shadow: inset 0 0 22px rgba(31, 116, 229, 0.2), 0 0 8px rgba(39, 135, 237, 0.25); |
|
|
|
} |
|
|
|
|
|
|
|
.month-title { |
|
|
|
font-size: 20px; |
|
|
|
font-weight: bold; |
|
|
|
margin-bottom: 15px; |
|
|
|
color: #35bcff; |
|
|
|
text-align: left; |
|
|
|
} |
|
|
|
|
|
|
|
.month-stats { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
gap: 8px; |
|
|
|
align-items: flex-end; |
|
|
|
margin-top: auto; |
|
|
|
} |
|
|
|
|
|
|
|
.month-stats.empty { |
|
|
|
text-align: center; |
|
|
|
padding: 20px 0; |
|
|
|
} |
|
|
|
|
|
|
|
.stat-item { |
|
|
|
display: flex; |
|
|
|
justify-content: flex-start; |
|
|
|
gap: 8px; |
|
|
|
min-width: 120px; |
|
|
|
font-size: 14px; |
|
|
|
color: #dceaff; |
|
|
|
text-align: left; |
|
|
|
} |
|
|
|
|
|
|
|
.status-executing { |
|
|
|
color: #fff; |
|
|
|
font-weight: bold; |
|
|
|
} |
|
|
|
|
|
|
|
.status-completed { |
|
|
|
color: #fff; |
|
|
|
font-weight: bold; |
|
|
|
} |
|
|
|
|
|
|
|
.status-not-executed { |
|
|
|
color: #fff; |
|
|
|
font-weight: bold; |
|
|
|
} |
|
|
|
|
|
|
|
/* 覆盖Element UI的默认样式 */ |
|
|
|
:deep(.el-date-editor--year) { |
|
|
|
background-color: #0b1d58; |
|
|
|
border: 1px solid #438fe5; |
|
|
|
border-radius: 0; |
|
|
|
} |
|
|
|
|
|
|
|
:deep(.el-input__inner) { |
|
|
|
background-color: #0b1d58; |
|
|
|
color: #dceaff; |
|
|
|
border: 1px solid #438fe5; |
|
|
|
} |
|
|
|
|
|
|
|
:deep(.el-input__inner::placeholder) { |
|
|
|
color: #7da8d8; |
|
|
|
} |
|
|
|
|
|
|
|
:deep(.el-date-picker__header-label) { |
|
|
|
color: #dceaff; |
|
|
|
} |
|
|
|
|
|
|
|
:deep(.el-date-picker__year-btn) { |
|
|
|
color: #dceaff; |
|
|
|
} |
|
|
|
|
|
|
|
:deep(.el-date-picker__year-btn:hover) { |
|
|
|
color: #1a53ff; |
|
|
|
} |
|
|
|
|
|
|
|
:deep(.el-picker-panel) { |
|
|
|
background-color: #0b1d58; |
|
|
|
border: 1px solid #438fe5; |
|
|
|
} |
|
|
|
.year-view { width: 100%; min-height: 100%; color: #e8f2ff; } |
|
|
|
.year-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; } |
|
|
|
.year-header h2 { margin: 0; font-size: 16px; font-weight: 400; color: #b9d6ff; } |
|
|
|
.year-picker { width: 100px; } |
|
|
|
.months-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; } |
|
|
|
.month-card { min-height: 220px; padding: 17px 22px; box-sizing: border-box; background: #17266e; border: 1px solid #2865c3; transition: border-color .2s, box-shadow .2s; } |
|
|
|
.month-card:hover { border-color: #43b9ff; box-shadow: 0 0 12px rgba(39, 135, 237, .28); } |
|
|
|
.month-title { color: #38b8ff; font-size: 25px; line-height: 1; font-weight: 500; } |
|
|
|
.month-body { display: flex; align-items: center; justify-content: space-between; height: 165px; } |
|
|
|
.month-stats { display: flex; flex-direction: column; gap: 8px; } |
|
|
|
.stat-item { display: flex; align-items: center; min-width: 145px; height: 23px; padding: 0 8px; box-sizing: border-box; color: #dbe8ff; font-size: 14px; background: rgba(24, 56, 130, .85); } |
|
|
|
.stat-item strong { margin-left: auto; font-weight: 400; color: #e5efff; } |
|
|
|
.status-dot { width: 8px; height: 8px; margin-right: 8px; border-radius: 50%; background: currentColor; flex: 0 0 auto; } |
|
|
|
.executing { color: #147fc1; }.completed { color: #178a70; }.pending { color: #536c9f; } |
|
|
|
.stat-label { color: #dbe8ff; } |
|
|
|
.month-icon { width: 82px; height: auto; margin: 12px 4px 0 8px; object-fit: contain; } |
|
|
|
:deep(.year-picker .el-input__inner) { height: 26px; padding: 0 8px; color: #dceaff; background: #10225f; border: 1px solid #438fe5; border-radius: 0; } |
|
|
|
:deep(.year-picker .el-input__prefix), :deep(.year-picker .el-input__suffix) { color: #a8c9ef; } |
|
|
|
@media (max-width: 1100px) { .months-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } } |
|
|
|
@media (max-width: 760px) { .months-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }.month-card { padding: 14px; }.month-icon { width: 65px; }.stat-item { min-width: 125px; font-size: 12px; } } |
|
|
|
</style> |