|
@@ -202,6 +202,31 @@
|
|
|
<div v-else ref="chart3" class="chart-body"></div>
|
|
<div v-else ref="chart3" class="chart-body"></div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
+ <!-- 图表10:各部门额外加班工时 -->
|
|
|
|
|
+ <div class="chart-card">
|
|
|
|
|
+ <div class="chart-title">
|
|
|
|
|
+ <span>各部门额外加班工时表</span>
|
|
|
|
|
+ <div class="chart-title-actions">
|
|
|
|
|
+ <el-radio-group
|
|
|
|
|
+ v-model="extraOvertimeTabMode"
|
|
|
|
|
+ size="mini"
|
|
|
|
|
+ :disabled="loading10"
|
|
|
|
|
+ @change="onExtraOvertimeTabChange"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-radio-button label="top">部门明细</el-radio-button>
|
|
|
|
|
+ <el-radio-button label="all">小组明细</el-radio-button>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-if="loading10" class="chart-loading">
|
|
|
|
|
+ <i class="el-icon-loading"></i> 加载中...
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-else-if="!hasExtraOvertimeData()" class="chart-empty">
|
|
|
|
|
+ 暂无数据
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div v-else ref="chart10" class="chart-body"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<!-- 图表4:各部门参与项目数量 -->
|
|
<!-- 图表4:各部门参与项目数量 -->
|
|
|
<div class="chart-card">
|
|
<div class="chart-card">
|
|
|
<div class="chart-title">
|
|
<div class="chart-title">
|
|
@@ -422,11 +447,16 @@ export default {
|
|
|
chart7: null,
|
|
chart7: null,
|
|
|
chart8: null,
|
|
chart8: null,
|
|
|
chart9: null,
|
|
chart9: null,
|
|
|
|
|
+ chart10: null,
|
|
|
top3PieMode: "working",
|
|
top3PieMode: "working",
|
|
|
top3DeptTabMode: "top",
|
|
top3DeptTabMode: "top",
|
|
|
top3DeptDataAll: [],
|
|
top3DeptDataAll: [],
|
|
|
deptHoursTabMode: "top",
|
|
deptHoursTabMode: "top",
|
|
|
deptHoursDataAll: [],
|
|
deptHoursDataAll: [],
|
|
|
|
|
+ extraOvertimeTabMode: "top",
|
|
|
|
|
+ extraOvertimeDeptData: [],
|
|
|
|
|
+ extraOvertimeDeptDataAll: [],
|
|
|
|
|
+ loading10: false,
|
|
|
userProjectTop10Data: [],
|
|
userProjectTop10Data: [],
|
|
|
loading9: false,
|
|
loading9: false,
|
|
|
};
|
|
};
|
|
@@ -448,7 +478,7 @@ export default {
|
|
|
this.contentScroller.removeEventListener("scroll", this.handleContentScroll);
|
|
this.contentScroller.removeEventListener("scroll", this.handleContentScroll);
|
|
|
}
|
|
}
|
|
|
this.removeWorkedProjectScrollListener();
|
|
this.removeWorkedProjectScrollListener();
|
|
|
- for (let i = 1; i <= 9; i++) {
|
|
|
|
|
|
|
+ for (let i = 1; i <= 10; i++) {
|
|
|
const chart = this[`chart${i}`];
|
|
const chart = this[`chart${i}`];
|
|
|
if (chart) {
|
|
if (chart) {
|
|
|
chart.dispose();
|
|
chart.dispose();
|
|
@@ -827,13 +857,14 @@ export default {
|
|
|
this.loadTop10Project();
|
|
this.loadTop10Project();
|
|
|
this.loadTop3ProjectDept();
|
|
this.loadTop3ProjectDept();
|
|
|
this.loadDeptHours();
|
|
this.loadDeptHours();
|
|
|
|
|
+ this.loadExtraOvertimeByDept();
|
|
|
this.loadDeptProjectCount();
|
|
this.loadDeptProjectCount();
|
|
|
this.loadDashboardAnalysis();
|
|
this.loadDashboardAnalysis();
|
|
|
this.loadUserProjectTop10();
|
|
this.loadUserProjectTop10();
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
disposeDashboardCharts() {
|
|
disposeDashboardCharts() {
|
|
|
- for (let i = 1; i <= 9; i++) {
|
|
|
|
|
|
|
+ for (let i = 1; i <= 10; i++) {
|
|
|
const chart = this[`chart${i}`];
|
|
const chart = this[`chart${i}`];
|
|
|
if (chart) {
|
|
if (chart) {
|
|
|
chart.dispose();
|
|
chart.dispose();
|
|
@@ -1004,6 +1035,71 @@ export default {
|
|
|
});
|
|
});
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
|
|
+ onExtraOvertimeTabChange() {
|
|
|
|
|
+ this.$nextTick(() => this.renderChart10());
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ hasExtraOvertimeData() {
|
|
|
|
|
+ const sourceData =
|
|
|
|
|
+ this.extraOvertimeTabMode === "top"
|
|
|
|
|
+ ? this.extraOvertimeDeptData
|
|
|
|
|
+ : this.extraOvertimeDeptDataAll;
|
|
|
|
|
+ return (sourceData || []).some((dept) => Number(dept.overtimeHours || 0) > 0);
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ loadExtraOvertimeByDept() {
|
|
|
|
|
+ const requestToken = this.getQueryToken();
|
|
|
|
|
+ this.loading10 = true;
|
|
|
|
|
+ this.extraOvertimeDeptData = [];
|
|
|
|
|
+ this.extraOvertimeDeptDataAll = [];
|
|
|
|
|
+
|
|
|
|
|
+ const loadMode = (deptMode, storeKey, callback) => {
|
|
|
|
|
+ this.http.post(
|
|
|
|
|
+ "/report/getDashboardExtraOvertimeByDept",
|
|
|
|
|
+ { ...this.getQueryParams(), deptMode },
|
|
|
|
|
+ (res) => {
|
|
|
|
|
+ if (!this.isCurrentQueryRequest(requestToken)) return;
|
|
|
|
|
+ if (res.code === "ok") {
|
|
|
|
|
+ const data = res.data || [];
|
|
|
|
|
+ const processData = (translated) => {
|
|
|
|
|
+ this[storeKey] = translated;
|
|
|
|
|
+ if (callback) callback();
|
|
|
|
|
+ };
|
|
|
|
|
+ if (this.needWxOpenData && data.length > 0) {
|
|
|
|
|
+ this.translateDeptNames(data, "departmentName", processData);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ processData(data);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.$message({ message: res.msg, type: "error" });
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ (err) => {
|
|
|
|
|
+ if (!this.isCurrentQueryRequest(requestToken)) return;
|
|
|
|
|
+ this.$message({ message: err, type: "error" });
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ let topDone = false;
|
|
|
|
|
+ let allDone = false;
|
|
|
|
|
+ const tryFinish = () => {
|
|
|
|
|
+ if (topDone && allDone) {
|
|
|
|
|
+ this.loading10 = false;
|
|
|
|
|
+ this.$nextTick(() => this.renderChart10());
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ loadMode("top", "extraOvertimeDeptData", () => {
|
|
|
|
|
+ topDone = true;
|
|
|
|
|
+ tryFinish();
|
|
|
|
|
+ });
|
|
|
|
|
+ loadMode(null, "extraOvertimeDeptDataAll", () => {
|
|
|
|
|
+ allDone = true;
|
|
|
|
|
+ tryFinish();
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
loadDeptProjectCount() {
|
|
loadDeptProjectCount() {
|
|
|
const requestToken = this.getQueryToken();
|
|
const requestToken = this.getQueryToken();
|
|
|
this.loading4 = true;
|
|
this.loading4 = true;
|
|
@@ -1802,6 +1898,189 @@ export default {
|
|
|
this.chart3.resize({ width, height });
|
|
this.chart3.resize({ width, height });
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
|
|
+ renderChart10() {
|
|
|
|
|
+ if (!this.$refs.chart10) return;
|
|
|
|
|
+ if (this.chart10) {
|
|
|
|
|
+ this.chart10.dispose();
|
|
|
|
|
+ this.chart10 = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ const el = this.$refs.chart10;
|
|
|
|
|
+ const parentEl = el.parentElement;
|
|
|
|
|
+ const width = parentEl ? parentEl.clientWidth - 32 : el.clientWidth;
|
|
|
|
|
+
|
|
|
|
|
+ const sourceData =
|
|
|
|
|
+ this.extraOvertimeTabMode === "top"
|
|
|
|
|
+ ? this.extraOvertimeDeptData
|
|
|
|
|
+ : this.extraOvertimeDeptDataAll;
|
|
|
|
|
+ const chartData = (sourceData || []).filter(
|
|
|
|
|
+ (item) => Number(item.overtimeHours || 0) > 0,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const depts = chartData.map((item) => item.departmentName);
|
|
|
|
|
+ const totalHours = chartData.map((item) =>
|
|
|
|
|
+ Number((item.overtimeHours || 0).toFixed(2)),
|
|
|
|
|
+ );
|
|
|
|
|
+ const avgHours = chartData.map((item) =>
|
|
|
|
|
+ Number((item.avgOvertimeHours || 0).toFixed(2)),
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const deptOpenIdMap10 = {};
|
|
|
|
|
+ chartData.forEach((item) => {
|
|
|
|
|
+ deptOpenIdMap10[item.departmentName] = item._deptOpenId || null;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const rowHeight = 28;
|
|
|
|
|
+ const minHeight = 300;
|
|
|
|
|
+ const isGroupMode = this.extraOvertimeTabMode !== "top";
|
|
|
|
|
+
|
|
|
|
|
+ if (isGroupMode) {
|
|
|
|
|
+ el.innerHTML = "";
|
|
|
|
|
+ const height = Math.max(minHeight, chartData.length * rowHeight + 100);
|
|
|
|
|
+ el.style.height = height + "px";
|
|
|
|
|
+ this.chart10 = echarts.init(el, null, { width, height });
|
|
|
|
|
+
|
|
|
|
|
+ const barMax = totalHours.length ? Math.max(...totalHours) : 0;
|
|
|
|
|
+ const lineMax = avgHours.length ? Math.max(...avgHours) : 0;
|
|
|
|
|
+ const xAxis0Max = Math.ceil(barMax * 1.15) || 10;
|
|
|
|
|
+ const xAxis1Max = Math.ceil(lineMax * 1.15) || 10;
|
|
|
|
|
+ const needTranslate = this.needWxOpenData;
|
|
|
|
|
+ const deptNameHtml = this.deptNameHtml.bind(this);
|
|
|
|
|
+
|
|
|
|
|
+ const option = {
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: "axis",
|
|
|
|
|
+ axisPointer: { type: "shadow" },
|
|
|
|
|
+ enterable: needTranslate,
|
|
|
|
|
+ formatter: (params) => {
|
|
|
|
|
+ const deptName = params[0].name;
|
|
|
|
|
+ const openId = deptOpenIdMap10[deptName] || null;
|
|
|
|
|
+ let result = deptNameHtml(deptName, openId) + "<br/>";
|
|
|
|
|
+ params.forEach((p) => {
|
|
|
|
|
+ result += p.marker + p.seriesName + ": " + p.value + " h<br/>";
|
|
|
|
|
+ });
|
|
|
|
|
+ return result;
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ data: ["加班总工时", "人均加班工时"],
|
|
|
|
|
+ top: 10,
|
|
|
|
|
+ textStyle: { fontSize: 11 },
|
|
|
|
|
+ },
|
|
|
|
|
+ grid: {
|
|
|
|
|
+ left: 86,
|
|
|
|
|
+ right: 48,
|
|
|
|
|
+ top: 58,
|
|
|
|
|
+ bottom: 44,
|
|
|
|
|
+ containLabel: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ xAxis: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "value",
|
|
|
|
|
+ name: "加班总工时",
|
|
|
|
|
+ nameLocation: "middle",
|
|
|
|
|
+ nameGap: 30,
|
|
|
|
|
+ nameTextStyle: { fontSize: 11 },
|
|
|
|
|
+ max: xAxis0Max,
|
|
|
|
|
+ axisLabel: { formatter: "{value} h" },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: "value",
|
|
|
|
|
+ name: "人均加班工时",
|
|
|
|
|
+ nameLocation: "middle",
|
|
|
|
|
+ nameGap: 30,
|
|
|
|
|
+ nameTextStyle: { fontSize: 11 },
|
|
|
|
|
+ max: xAxis1Max,
|
|
|
|
|
+ position: "top",
|
|
|
|
|
+ axisLabel: { formatter: "{value} h" },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: "category",
|
|
|
|
|
+ data: depts,
|
|
|
|
|
+ inverse: true,
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ interval: 0,
|
|
|
|
|
+ fontSize: 11,
|
|
|
|
|
+ margin: 12,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "加班总工时",
|
|
|
|
|
+ type: "bar",
|
|
|
|
|
+ xAxisIndex: 0,
|
|
|
|
|
+ data: totalHours,
|
|
|
|
|
+ itemStyle: { color: "#5470C6" },
|
|
|
|
|
+ barMaxWidth: 18,
|
|
|
|
|
+ label: {
|
|
|
|
|
+ normal: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ position: "right",
|
|
|
|
|
+ distance: 4,
|
|
|
|
|
+ formatter: (params) => {
|
|
|
|
|
+ const val = Number(params.value || 0);
|
|
|
|
|
+ return val === 0 ? "" : val + " h";
|
|
|
|
|
+ },
|
|
|
|
|
+ color: "#1f2d3d",
|
|
|
|
|
+ fontSize: 10,
|
|
|
|
|
+ fontWeight: "600",
|
|
|
|
|
+ backgroundColor: "rgba(255,255,255,0.85)",
|
|
|
|
|
+ borderColor: "#ddd",
|
|
|
|
|
+ borderWidth: 1,
|
|
|
|
|
+ borderRadius: 3,
|
|
|
|
|
+ padding: [2, 4],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: "人均加班工时",
|
|
|
|
|
+ type: "line",
|
|
|
|
|
+ xAxisIndex: 1,
|
|
|
|
|
+ data: avgHours,
|
|
|
|
|
+ itemStyle: { color: "#FF7F0E" },
|
|
|
|
|
+ lineStyle: { width: 2 },
|
|
|
|
|
+ symbol: "circle",
|
|
|
|
|
+ symbolSize: 6,
|
|
|
|
|
+ label: {
|
|
|
|
|
+ normal: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ position: "right",
|
|
|
|
|
+ distance: 6,
|
|
|
|
|
+ formatter: (params) => {
|
|
|
|
|
+ const val = Number(params.value || 0);
|
|
|
|
|
+ return val === 0 ? "" : val + " h";
|
|
|
|
|
+ },
|
|
|
|
|
+ fontSize: 10,
|
|
|
|
|
+ color: "#303133",
|
|
|
|
|
+ backgroundColor: "rgba(255,255,255,0.78)",
|
|
|
|
|
+ padding: [1, 3],
|
|
|
|
|
+ borderRadius: 3,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ };
|
|
|
|
|
+ this.chart10.setOption(option, true);
|
|
|
|
|
+ this.chart10.resize({ width, height });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const height = Math.max(minHeight, chartData.length * rowHeight + 100);
|
|
|
|
|
+ el.style.height = height + "px";
|
|
|
|
|
+ this.chart10 = echarts.init(el, null, { width, height });
|
|
|
|
|
+
|
|
|
|
|
+ const option = this.buildBarLineOption({
|
|
|
|
|
+ names: depts,
|
|
|
|
|
+ barSeries: [{ name: "加班总工时", data: totalHours, color: "#5470C6" }],
|
|
|
|
|
+ lineSeries: [{ name: "人均加班工时", data: avgHours, color: "#FF7F0E" }],
|
|
|
|
|
+ yAxisNames: ["加班总工时", "人均加班工时"],
|
|
|
|
|
+ visibleSize: depts.length || 8,
|
|
|
|
|
+ deptOpenIdMap: deptOpenIdMap10,
|
|
|
|
|
+ });
|
|
|
|
|
+ this.chart10.setOption(option);
|
|
|
|
|
+ this.chart10.resize({ width, height });
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
renderChart4() {
|
|
renderChart4() {
|
|
|
if (!this.$refs.chart4) return;
|
|
if (!this.$refs.chart4) return;
|
|
|
if (this.chart4) {
|
|
if (this.chart4) {
|
|
@@ -2323,6 +2602,7 @@ export default {
|
|
|
if (this.top10Data.length > 0) this.renderChart1();
|
|
if (this.top10Data.length > 0) this.renderChart1();
|
|
|
if (this.top3DeptData.length > 0) this.renderChart2();
|
|
if (this.top3DeptData.length > 0) this.renderChart2();
|
|
|
if (this.deptHoursData.length > 0) this.renderChart3();
|
|
if (this.deptHoursData.length > 0) this.renderChart3();
|
|
|
|
|
+ if (this.hasExtraOvertimeData()) this.renderChart10();
|
|
|
if (this.deptProjectCountData.length > 0) this.renderChart4();
|
|
if (this.deptProjectCountData.length > 0) this.renderChart4();
|
|
|
if (this.projectOvertimeTop5.length > 0) this.renderChart5();
|
|
if (this.projectOvertimeTop5.length > 0) this.renderChart5();
|
|
|
if (this.hasTop3OvertimeChartData()) this.renderChart6();
|
|
if (this.hasTop3OvertimeChartData()) this.renderChart6();
|