|
@@ -1,4 +1,4 @@
|
|
|
-<template>
|
|
|
|
|
|
|
+<template>
|
|
|
<div class="dashboard-container" ref="dashboardContainer">
|
|
<div class="dashboard-container" ref="dashboardContainer">
|
|
|
<!-- 顶部标题和日期选择 -->
|
|
<!-- 顶部标题和日期选择 -->
|
|
|
<div class="dashboard-header">
|
|
<div class="dashboard-header">
|
|
@@ -137,7 +137,7 @@
|
|
|
<el-radio-group
|
|
<el-radio-group
|
|
|
v-model="top3PieMode"
|
|
v-model="top3PieMode"
|
|
|
size="mini"
|
|
size="mini"
|
|
|
- :disabled="loading2 || top3DeptData.length === 0"
|
|
|
|
|
|
|
+ :disabled="loading2 || !hasTop3DeptChartData()"
|
|
|
@change="renderChart2"
|
|
@change="renderChart2"
|
|
|
>
|
|
>
|
|
|
<el-radio-button label="working">总工时</el-radio-button>
|
|
<el-radio-button label="working">总工时</el-radio-button>
|
|
@@ -149,7 +149,7 @@
|
|
|
<i class="el-icon-loading"></i> 加载中...
|
|
<i class="el-icon-loading"></i> 加载中...
|
|
|
</div>
|
|
</div>
|
|
|
<div
|
|
<div
|
|
|
- v-else-if="top3DeptData.length === 0"
|
|
|
|
|
|
|
+ v-else-if="!hasTop3DeptChartData()"
|
|
|
class="chart-empty project-rank-loading"
|
|
class="chart-empty project-rank-loading"
|
|
|
>
|
|
>
|
|
|
暂无数据
|
|
暂无数据
|
|
@@ -661,6 +661,7 @@ export default {
|
|
|
|
|
|
|
|
// ========== 数据加载 ==========
|
|
// ========== 数据加载 ==========
|
|
|
loadAllCharts() {
|
|
loadAllCharts() {
|
|
|
|
|
+ this.disposeDashboardCharts();
|
|
|
this.loadTop10Project();
|
|
this.loadTop10Project();
|
|
|
this.loadTop3ProjectDept();
|
|
this.loadTop3ProjectDept();
|
|
|
this.loadDeptHours();
|
|
this.loadDeptHours();
|
|
@@ -669,13 +670,36 @@ export default {
|
|
|
this.loadUserProjectTop10();
|
|
this.loadUserProjectTop10();
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
|
|
+ disposeDashboardCharts() {
|
|
|
|
|
+ for (let i = 1; i <= 9; i++) {
|
|
|
|
|
+ const chart = this[`chart${i}`];
|
|
|
|
|
+ if (chart) {
|
|
|
|
|
+ chart.dispose();
|
|
|
|
|
+ this[`chart${i}`] = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ hasTop3DeptChartData() {
|
|
|
|
|
+ const valueKey = this.top3PieMode === "overtime" ? "overtimeHours" : "workingTime";
|
|
|
|
|
+ return (this.top3DeptData || []).some((dept) =>
|
|
|
|
|
+ (dept.items || []).some((item) => Number(item[valueKey] || 0) > 0),
|
|
|
|
|
+ );
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ isCurrentMonthRequest(month) {
|
|
|
|
|
+ return month === this.selectedMonth;
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
loadTop10Project() {
|
|
loadTop10Project() {
|
|
|
|
|
+ const requestMonth = this.selectedMonth;
|
|
|
this.loading1 = true;
|
|
this.loading1 = true;
|
|
|
this.top10Data = [];
|
|
this.top10Data = [];
|
|
|
this.http.post(
|
|
this.http.post(
|
|
|
"/report/getTop10ProjectReport",
|
|
"/report/getTop10ProjectReport",
|
|
|
- { ymonth: this.selectedMonth },
|
|
|
|
|
|
|
+ { ymonth: requestMonth },
|
|
|
(res) => {
|
|
(res) => {
|
|
|
|
|
+ if (!this.isCurrentMonthRequest(requestMonth)) return;
|
|
|
this.loading1 = false;
|
|
this.loading1 = false;
|
|
|
if (res.code === "ok") {
|
|
if (res.code === "ok") {
|
|
|
this.top10Data = res.data || [];
|
|
this.top10Data = res.data || [];
|
|
@@ -685,6 +709,7 @@ export default {
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
(err) => {
|
|
(err) => {
|
|
|
|
|
+ if (!this.isCurrentMonthRequest(requestMonth)) return;
|
|
|
this.loading1 = false;
|
|
this.loading1 = false;
|
|
|
this.$message({ message: err, type: "error" });
|
|
this.$message({ message: err, type: "error" });
|
|
|
},
|
|
},
|
|
@@ -692,12 +717,14 @@ export default {
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
loadTop3ProjectDept() {
|
|
loadTop3ProjectDept() {
|
|
|
|
|
+ const requestMonth = this.selectedMonth;
|
|
|
this.loading2 = true;
|
|
this.loading2 = true;
|
|
|
this.top3DeptData = [];
|
|
this.top3DeptData = [];
|
|
|
this.http.post(
|
|
this.http.post(
|
|
|
"/report/getTop3ProjectReportGroupByDept",
|
|
"/report/getTop3ProjectReportGroupByDept",
|
|
|
- { ymonth: this.selectedMonth },
|
|
|
|
|
|
|
+ { ymonth: requestMonth },
|
|
|
(res) => {
|
|
(res) => {
|
|
|
|
|
+ if (!this.isCurrentMonthRequest(requestMonth)) return;
|
|
|
this.loading2 = false;
|
|
this.loading2 = false;
|
|
|
if (res.code === "ok") {
|
|
if (res.code === "ok") {
|
|
|
const data = res.data || [];
|
|
const data = res.data || [];
|
|
@@ -715,6 +742,7 @@ export default {
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
(err) => {
|
|
(err) => {
|
|
|
|
|
+ if (!this.isCurrentMonthRequest(requestMonth)) return;
|
|
|
this.loading2 = false;
|
|
this.loading2 = false;
|
|
|
this.$message({ message: err, type: "error" });
|
|
this.$message({ message: err, type: "error" });
|
|
|
},
|
|
},
|
|
@@ -722,12 +750,14 @@ export default {
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
loadDeptHours() {
|
|
loadDeptHours() {
|
|
|
|
|
+ const requestMonth = this.selectedMonth;
|
|
|
this.loading3 = true;
|
|
this.loading3 = true;
|
|
|
this.deptHoursData = [];
|
|
this.deptHoursData = [];
|
|
|
this.http.post(
|
|
this.http.post(
|
|
|
"/report/getProjectReportGroupByDept",
|
|
"/report/getProjectReportGroupByDept",
|
|
|
- { ymonth: this.selectedMonth },
|
|
|
|
|
|
|
+ { ymonth: requestMonth },
|
|
|
(res) => {
|
|
(res) => {
|
|
|
|
|
+ if (!this.isCurrentMonthRequest(requestMonth)) return;
|
|
|
this.loading3 = false;
|
|
this.loading3 = false;
|
|
|
if (res.code === "ok") {
|
|
if (res.code === "ok") {
|
|
|
const data = res.data || [];
|
|
const data = res.data || [];
|
|
@@ -745,6 +775,7 @@ export default {
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
(err) => {
|
|
(err) => {
|
|
|
|
|
+ if (!this.isCurrentMonthRequest(requestMonth)) return;
|
|
|
this.loading3 = false;
|
|
this.loading3 = false;
|
|
|
this.$message({ message: err, type: "error" });
|
|
this.$message({ message: err, type: "error" });
|
|
|
},
|
|
},
|
|
@@ -752,12 +783,14 @@ export default {
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
loadDeptProjectCount() {
|
|
loadDeptProjectCount() {
|
|
|
|
|
+ const requestMonth = this.selectedMonth;
|
|
|
this.loading4 = true;
|
|
this.loading4 = true;
|
|
|
this.deptProjectCountData = [];
|
|
this.deptProjectCountData = [];
|
|
|
this.http.post(
|
|
this.http.post(
|
|
|
"/report/getDeptProjectCount",
|
|
"/report/getDeptProjectCount",
|
|
|
- { ymonth: this.selectedMonth },
|
|
|
|
|
|
|
+ { ymonth: requestMonth },
|
|
|
(res) => {
|
|
(res) => {
|
|
|
|
|
+ if (!this.isCurrentMonthRequest(requestMonth)) return;
|
|
|
this.loading4 = false;
|
|
this.loading4 = false;
|
|
|
if (res.code === "ok") {
|
|
if (res.code === "ok") {
|
|
|
const data = res.data || [];
|
|
const data = res.data || [];
|
|
@@ -775,6 +808,7 @@ export default {
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
(err) => {
|
|
(err) => {
|
|
|
|
|
+ if (!this.isCurrentMonthRequest(requestMonth)) return;
|
|
|
this.loading4 = false;
|
|
this.loading4 = false;
|
|
|
this.$message({ message: err, type: "error" });
|
|
this.$message({ message: err, type: "error" });
|
|
|
},
|
|
},
|
|
@@ -782,12 +816,14 @@ export default {
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
loadDashboardAnalysis() {
|
|
loadDashboardAnalysis() {
|
|
|
|
|
+ const requestMonth = this.selectedMonth;
|
|
|
this.loading5 = true;
|
|
this.loading5 = true;
|
|
|
this.analysisData = {};
|
|
this.analysisData = {};
|
|
|
this.http.post(
|
|
this.http.post(
|
|
|
"/report/getDashboardAnalysisReport",
|
|
"/report/getDashboardAnalysisReport",
|
|
|
- { ymonth: this.selectedMonth },
|
|
|
|
|
|
|
+ { ymonth: requestMonth },
|
|
|
(res) => {
|
|
(res) => {
|
|
|
|
|
+ if (!this.isCurrentMonthRequest(requestMonth)) return;
|
|
|
this.loading5 = false;
|
|
this.loading5 = false;
|
|
|
if (res.code === "ok") {
|
|
if (res.code === "ok") {
|
|
|
this.analysisData = res.data || {};
|
|
this.analysisData = res.data || {};
|
|
@@ -804,6 +840,7 @@ export default {
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
(err) => {
|
|
(err) => {
|
|
|
|
|
+ if (!this.isCurrentMonthRequest(requestMonth)) return;
|
|
|
this.loading5 = false;
|
|
this.loading5 = false;
|
|
|
this.$message({ message: err, type: "error" });
|
|
this.$message({ message: err, type: "error" });
|
|
|
},
|
|
},
|
|
@@ -811,12 +848,14 @@ export default {
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
loadUserProjectTop10() {
|
|
loadUserProjectTop10() {
|
|
|
|
|
+ const requestMonth = this.selectedMonth;
|
|
|
this.loading9 = true;
|
|
this.loading9 = true;
|
|
|
this.userProjectTop10Data = [];
|
|
this.userProjectTop10Data = [];
|
|
|
this.http.post(
|
|
this.http.post(
|
|
|
"/report/getUserProjectTop10",
|
|
"/report/getUserProjectTop10",
|
|
|
- { ymonth: this.selectedMonth },
|
|
|
|
|
|
|
+ { ymonth: requestMonth },
|
|
|
(res) => {
|
|
(res) => {
|
|
|
|
|
+ if (!this.isCurrentMonthRequest(requestMonth)) return;
|
|
|
this.loading9 = false;
|
|
this.loading9 = false;
|
|
|
if (res.code === "ok") {
|
|
if (res.code === "ok") {
|
|
|
const data = res.data || [];
|
|
const data = res.data || [];
|
|
@@ -834,6 +873,7 @@ export default {
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
(err) => {
|
|
(err) => {
|
|
|
|
|
+ if (!this.isCurrentMonthRequest(requestMonth)) return;
|
|
|
this.loading9 = false;
|
|
this.loading9 = false;
|
|
|
this.$message({ message: err, type: "error" });
|
|
this.$message({ message: err, type: "error" });
|
|
|
},
|
|
},
|
|
@@ -998,7 +1038,7 @@ export default {
|
|
|
position: "top",
|
|
position: "top",
|
|
|
distance: 4,
|
|
distance: 4,
|
|
|
formatter: (params) => {
|
|
formatter: (params) => {
|
|
|
- const val = Number(params.value);
|
|
|
|
|
|
|
+ const val = Number(params.value || 0);
|
|
|
if (val === 0) return "";
|
|
if (val === 0) return "";
|
|
|
return val + " " + unit;
|
|
return val + " " + unit;
|
|
|
},
|
|
},
|
|
@@ -1242,39 +1282,127 @@ export default {
|
|
|
this.chart2 = null;
|
|
this.chart2 = null;
|
|
|
}
|
|
}
|
|
|
const el = this.$refs.chart2;
|
|
const el = this.$refs.chart2;
|
|
|
|
|
+ el.innerHTML = "";
|
|
|
const parentEl = el.parentElement;
|
|
const parentEl = el.parentElement;
|
|
|
const width = parentEl ? parentEl.clientWidth - 32 : el.clientWidth;
|
|
const width = parentEl ? parentEl.clientWidth - 32 : el.clientWidth;
|
|
|
this.chart2 = echarts.init(el, null, { width, height: 380 });
|
|
this.chart2 = echarts.init(el, null, { width, height: 380 });
|
|
|
|
|
|
|
|
const isOvertime = this.top3PieMode === "overtime";
|
|
const isOvertime = this.top3PieMode === "overtime";
|
|
|
- const names = this.top3DeptData.map((item) => item.departmentName);
|
|
|
|
|
- const workingTimes = this.top3DeptData.map((item) =>
|
|
|
|
|
- Number(item.workingTime || 0)
|
|
|
|
|
- );
|
|
|
|
|
- const overtimeTimes = this.top3DeptData.map((item) =>
|
|
|
|
|
- Number(item.overtimeHours || 0)
|
|
|
|
|
|
|
+ const valueKey = isOvertime ? "overtimeHours" : "workingTime";
|
|
|
|
|
+ const chartData = this.top3DeptData.filter((dept) =>
|
|
|
|
|
+ (dept.items || []).some((item) => Number(item[valueKey] || 0) > 0),
|
|
|
);
|
|
);
|
|
|
-
|
|
|
|
|
|
|
+ const depts = chartData.map((item) => item.departmentName);
|
|
|
const deptOpenIdMap2 = {};
|
|
const deptOpenIdMap2 = {};
|
|
|
- this.top3DeptData.forEach((item) => {
|
|
|
|
|
- deptOpenIdMap2[item.departmentName] = item._deptOpenId || null;
|
|
|
|
|
|
|
+ chartData.forEach((item) => {
|
|
|
|
|
+ const openId = item._deptOpenId || this.getDeptOpenId(item);
|
|
|
|
|
+ deptOpenIdMap2[item.departmentName] = openId || null;
|
|
|
});
|
|
});
|
|
|
|
|
+ const rankSeries = [
|
|
|
|
|
+ { rank: 0, name: "项目TOP1" },
|
|
|
|
|
+ { rank: 1, name: "项目TOP2" },
|
|
|
|
|
+ { rank: 2, name: "项目TOP3" },
|
|
|
|
|
+ ];
|
|
|
|
|
+ const getDeptRankItem = (dept, rank) =>
|
|
|
|
|
+ (dept.items || [])
|
|
|
|
|
+ .slice()
|
|
|
|
|
+ .sort(
|
|
|
|
|
+ (left, right) =>
|
|
|
|
|
+ Number(left.projectRank || 0) - Number(right.projectRank || 0),
|
|
|
|
|
+ )[rank];
|
|
|
|
|
+ const maxValue = Math.max(
|
|
|
|
|
+ ...chartData.flatMap((dept) =>
|
|
|
|
|
+ (dept.items || []).map((project) => Number(project[valueKey] || 0)),
|
|
|
|
|
+ ),
|
|
|
|
|
+ 0,
|
|
|
|
|
+ );
|
|
|
|
|
+ const xAxisMax = Math.ceil(maxValue * 1.15) || 10;
|
|
|
|
|
+ const needTranslate = this.needWxOpenData;
|
|
|
|
|
+ const deptNameHtml = this.deptNameHtml.bind(this);
|
|
|
|
|
|
|
|
- const option = this.buildBarLineOption({
|
|
|
|
|
- names,
|
|
|
|
|
- barSeries: [
|
|
|
|
|
- {
|
|
|
|
|
- name: isOvertime ? "加班工时" : "总工时",
|
|
|
|
|
- data: isOvertime ? overtimeTimes : workingTimes,
|
|
|
|
|
- color: isOvertime ? "#FF7F0E" : "#5470C6",
|
|
|
|
|
|
|
+ const option = {
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: "axis",
|
|
|
|
|
+ axisPointer: { type: "shadow" },
|
|
|
|
|
+ enterable: needTranslate,
|
|
|
|
|
+ formatter: (params) => {
|
|
|
|
|
+ const deptName = params[0].name;
|
|
|
|
|
+ const openId = deptOpenIdMap2[deptName] || null;
|
|
|
|
|
+ let result = deptNameHtml(deptName, openId) + "<br/>";
|
|
|
|
|
+ params.forEach((p) => {
|
|
|
|
|
+ const item = p.data || {};
|
|
|
|
|
+ const value = Number(item.value || 0);
|
|
|
|
|
+ if (!value) return;
|
|
|
|
|
+ const projectText = item.projectCode || item.projectName || p.seriesName;
|
|
|
|
|
+ result += p.marker + projectText + ": " + value + " h<br/>";
|
|
|
|
|
+ });
|
|
|
|
|
+ return result;
|
|
|
},
|
|
},
|
|
|
- ],
|
|
|
|
|
- lineSeries: [],
|
|
|
|
|
- yAxisNames: [isOvertime ? "加班工时" : "总工时", ""],
|
|
|
|
|
- visibleSize: Number.MAX_SAFE_INTEGER,
|
|
|
|
|
- deptOpenIdMap: deptOpenIdMap2,
|
|
|
|
|
- });
|
|
|
|
|
- this.chart2.setOption(option);
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ data: rankSeries.map((series) => series.name),
|
|
|
|
|
+ bottom: 8,
|
|
|
|
|
+ textStyle: { fontSize: 11 },
|
|
|
|
|
+ },
|
|
|
|
|
+ grid: {
|
|
|
|
|
+ left: 86,
|
|
|
|
|
+ right: 112,
|
|
|
|
|
+ top: 36,
|
|
|
|
|
+ bottom: 52,
|
|
|
|
|
+ containLabel: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: "value",
|
|
|
|
|
+ max: xAxisMax,
|
|
|
|
|
+ axisLabel: { formatter: "{value} h" },
|
|
|
|
|
+ },
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: "category",
|
|
|
|
|
+ data: depts,
|
|
|
|
|
+ inverse: true,
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ interval: 0,
|
|
|
|
|
+ fontSize: 11,
|
|
|
|
|
+ margin: 12,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ series: rankSeries.map((rankConfig) => ({
|
|
|
|
|
+ name: rankConfig.name,
|
|
|
|
|
+ type: "bar",
|
|
|
|
|
+ data: chartData.map((dept) => {
|
|
|
|
|
+ const matched = getDeptRankItem(dept, rankConfig.rank);
|
|
|
|
|
+ return matched
|
|
|
|
|
+ ? {
|
|
|
|
|
+ value: Number(matched[valueKey] || 0),
|
|
|
|
|
+ projectCode: matched.projectCode,
|
|
|
|
|
+ projectName: matched.projectName,
|
|
|
|
|
+ }
|
|
|
|
|
+ : { value: 0 };
|
|
|
|
|
+ }),
|
|
|
|
|
+ barMaxWidth: 14,
|
|
|
|
|
+ label: {
|
|
|
|
|
+ normal: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ position: "right",
|
|
|
|
|
+ distance: 4,
|
|
|
|
|
+ formatter: (params) => {
|
|
|
|
|
+ const val = Number(params.value);
|
|
|
|
|
+ if (val === 0) return "";
|
|
|
|
|
+ return val + " h";
|
|
|
|
|
+ },
|
|
|
|
|
+ color: "#1f2d3d",
|
|
|
|
|
+ fontSize: 10,
|
|
|
|
|
+ fontWeight: "600",
|
|
|
|
|
+ backgroundColor: "rgba(255,255,255,0.85)",
|
|
|
|
|
+ borderColor: "#ddd",
|
|
|
|
|
+ borderWidth: 1,
|
|
|
|
|
+ borderRadius: 3,
|
|
|
|
|
+ padding: [1, 3],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ })),
|
|
|
|
|
+ };
|
|
|
|
|
+ this.chart2.setOption(option, true);
|
|
|
this.chart2.resize({ width, height: 380 });
|
|
this.chart2.resize({ width, height: 380 });
|
|
|
},
|
|
},
|
|
|
|
|
|
|
@@ -1929,6 +2057,7 @@ export default {
|
|
|
color: #8b9bad;
|
|
color: #8b9bad;
|
|
|
font-size: 14px;
|
|
font-size: 14px;
|
|
|
height: 300px;
|
|
height: 300px;
|
|
|
|
|
+ line-height: 24px;
|
|
|
border-radius: 10px;
|
|
border-radius: 10px;
|
|
|
background: repeating-linear-gradient(
|
|
background: repeating-linear-gradient(
|
|
|
45deg,
|
|
45deg,
|
|
@@ -1939,6 +2068,17 @@ export default {
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.chart-loading .el-icon-loading {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 18px;
|
|
|
|
|
+ height: 18px;
|
|
|
|
|
+ line-height: 18px;
|
|
|
|
|
+ margin-right: 6px;
|
|
|
|
|
+ overflow: visible;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.analysis-section {
|
|
.analysis-section {
|
|
|
margin-top: 18px;
|
|
margin-top: 18px;
|
|
|
background: rgba(255, 255, 255, 0.96);
|
|
background: rgba(255, 255, 255, 0.96);
|