|
|
@ -47,7 +47,7 @@ |
|
|
<div id="overviewPieChart" class="chart-box"></div> |
|
|
<div id="overviewPieChart" class="chart-box"></div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="panel wide"> |
|
|
<div class="panel wide"> |
|
|
<div class="panel-title">最近 7 天完工趋势</div> |
|
|
|
|
|
|
|
|
<div class="panel-title">平均生产周期(天)</div> |
|
|
<div id="overviewLineChart" class="chart-box line-box"></div> |
|
|
<div id="overviewLineChart" class="chart-box line-box"></div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="panel"> |
|
|
<div class="panel"> |
|
|
@ -91,6 +91,16 @@ const NODE_DISPLAY_NAME_ALIAS = { |
|
|
线缆生产: '线缆/COP', |
|
|
线缆生产: '线缆/COP', |
|
|
COP生产: '线缆/COP' |
|
|
COP生产: '线缆/COP' |
|
|
} |
|
|
} |
|
|
|
|
|
const AVERAGE_CYCLE_CATEGORY_LIST = [ |
|
|
|
|
|
{ key: 'VW', name: 'VW', orderType: 'HOME_LIFT' }, |
|
|
|
|
|
{ key: 'VC', name: 'VC', orderType: 'HOME_LIFT' }, |
|
|
|
|
|
{ key: 'Y18', name: 'Y18', orderType: 'HOME_LIFT' }, |
|
|
|
|
|
{ key: 'VL3.0', name: 'VL3.0', orderType: 'HOME_LIFT' }, |
|
|
|
|
|
{ key: 'VC2.0', name: 'VC2.0', orderType: 'HOME_LIFT' }, |
|
|
|
|
|
{ key: 'Y18 2.0', name: 'Y18 2.0', orderType: 'HOME_LIFT' }, |
|
|
|
|
|
{ key: 'VL2.5', name: 'VL2.5', orderType: 'RENOVATION' }, |
|
|
|
|
|
{ key: '机加工', name: '机加工', orderType: 'MACHINING' } |
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
export default { |
|
|
export default { |
|
|
name: 'ScreenFactoryOverview', |
|
|
name: 'ScreenFactoryOverview', |
|
|
@ -164,6 +174,10 @@ export default { |
|
|
return (sourceList || []) |
|
|
return (sourceList || []) |
|
|
.filter(item => STATUS_ALLOW_LIST.includes(item.status)) |
|
|
.filter(item => STATUS_ALLOW_LIST.includes(item.status)) |
|
|
.map(item => ({ |
|
|
.map(item => ({ |
|
|
|
|
|
orderNo: item.orderNo || '', |
|
|
|
|
|
taskNo: item.taskNo || '', |
|
|
|
|
|
projectNo: item.projectNo || '', |
|
|
|
|
|
modelNo: item.modelNo || '', |
|
|
status: item.status || '', |
|
|
status: item.status || '', |
|
|
planDeliveryDate: item.planDeliveryDate || '', |
|
|
planDeliveryDate: item.planDeliveryDate || '', |
|
|
planFinishDate: item.planFinishDate || '', |
|
|
planFinishDate: item.planFinishDate || '', |
|
|
@ -213,7 +227,7 @@ export default { |
|
|
this.$nextTick(() => { |
|
|
this.$nextTick(() => { |
|
|
this.renderBarChart() |
|
|
this.renderBarChart() |
|
|
this.renderPieChart() |
|
|
this.renderPieChart() |
|
|
this.renderLineChart() |
|
|
|
|
|
|
|
|
this.renderCycleChart() |
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
|
countByMonth(list, monthStart) { |
|
|
countByMonth(list, monthStart) { |
|
|
@ -416,44 +430,175 @@ export default { |
|
|
}] |
|
|
}] |
|
|
}, true) |
|
|
}, true) |
|
|
}, |
|
|
}, |
|
|
renderLineChart() { |
|
|
|
|
|
|
|
|
renderCycleChart() { |
|
|
const el = document.getElementById('overviewLineChart') |
|
|
const el = document.getElementById('overviewLineChart') |
|
|
if (!el) return |
|
|
if (!el) return |
|
|
if (!this.chartLine) this.chartLine = echarts.init(el) |
|
|
if (!this.chartLine) this.chartLine = echarts.init(el) |
|
|
const xAxis = [] |
|
|
|
|
|
const wholeSeries = [] |
|
|
|
|
|
const renovationSeries = [] |
|
|
|
|
|
const taskSeries = [] |
|
|
|
|
|
const machiningSeries = [] |
|
|
|
|
|
for (let i = 6; i >= 0; i--) { |
|
|
|
|
|
const day = this.dayjs().subtract(i, 'day') |
|
|
|
|
|
xAxis.push(day.format('MM-DD')) |
|
|
|
|
|
wholeSeries.push(this.countFinishByDay(this.wholeList, day)) |
|
|
|
|
|
renovationSeries.push(this.countFinishByDay(this.renovationList, day)) |
|
|
|
|
|
taskSeries.push(this.countFinishByDay(this.taskList, day)) |
|
|
|
|
|
machiningSeries.push(this.countFinishByDay(this.machiningList, day)) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const cycleCategoryList = this.buildAverageCycleCategoryList() |
|
|
this.chartLine.setOption({ |
|
|
this.chartLine.setOption({ |
|
|
animation: false, |
|
|
animation: false, |
|
|
color: ['#6cc7ff', '#74e0b3', '#7fa6d9', '#f5c15e'], |
|
|
|
|
|
grid: { left: 35, right: 20, top: 34, bottom: 20, containLabel: true }, |
|
|
|
|
|
tooltip: { trigger: 'axis' }, |
|
|
|
|
|
legend: { textStyle: { color: '#cbe7ff', fontSize: 13 }, data: ['整梯完工', 'VL2.5升级完工', '线缆/COP完工', '机加工完工'] }, |
|
|
|
|
|
xAxis: { type: 'category', axisLabel: { color: '#cbe7ff', fontSize: 13 }, data: xAxis }, |
|
|
|
|
|
yAxis: { type: 'value', axisLabel: { color: '#cbe7ff', fontSize: 13 }, splitLine: { lineStyle: { color: 'rgba(160,200,240,0.15)' } } }, |
|
|
|
|
|
|
|
|
color: ['#4f73bd'], |
|
|
|
|
|
grid: { left: 35, right: 20, top: 34, bottom: 26, containLabel: true }, |
|
|
|
|
|
tooltip: { |
|
|
|
|
|
trigger: 'axis', |
|
|
|
|
|
formatter: (params) => { |
|
|
|
|
|
const point = (params && params[0]) || {} |
|
|
|
|
|
return `${point.axisValue}<br/>平均生产周期:${point.value || 0} 天` |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
xAxis: { |
|
|
|
|
|
type: 'category', |
|
|
|
|
|
axisTick: { alignWithLabel: true }, |
|
|
|
|
|
axisLabel: { color: '#cbe7ff', fontSize: 13, interval: 0 }, |
|
|
|
|
|
axisLine: { lineStyle: { color: 'rgba(160,200,240,0.35)' } }, |
|
|
|
|
|
data: cycleCategoryList.map(item => item.name) |
|
|
|
|
|
}, |
|
|
|
|
|
yAxis: { |
|
|
|
|
|
type: 'value', |
|
|
|
|
|
min: 0, |
|
|
|
|
|
minInterval: 1, |
|
|
|
|
|
max: (value) => { |
|
|
|
|
|
const maxValue = Number(value && value.max ? value.max : 0) |
|
|
|
|
|
if (maxValue <= 0) { |
|
|
|
|
|
return 5 |
|
|
|
|
|
} |
|
|
|
|
|
return maxValue + Math.max(1, Math.ceil(maxValue * 0.15)) |
|
|
|
|
|
}, |
|
|
|
|
|
axisLabel: { color: '#cbe7ff', fontSize: 13 }, |
|
|
|
|
|
splitLine: { lineStyle: { color: 'rgba(160,200,240,0.15)' } } |
|
|
|
|
|
}, |
|
|
series: [ |
|
|
series: [ |
|
|
{ name: '整梯完工', type: 'line', smooth: true, symbol: 'circle', symbolSize: 7, data: wholeSeries, itemStyle: { color: '#6cc7ff' }, lineStyle: { color: '#6cc7ff' }, areaStyle: { color: 'rgba(108,199,255,0.18)' } }, |
|
|
|
|
|
{ name: 'VL2.5升级完工', type: 'line', smooth: true, symbol: 'circle', symbolSize: 7, data: renovationSeries, itemStyle: { color: '#74e0b3' }, lineStyle: { color: '#74e0b3' }, areaStyle: { color: 'rgba(116,224,179,0.18)' } }, |
|
|
|
|
|
{ name: '线缆/COP完工', type: 'line', smooth: true, symbol: 'circle', symbolSize: 7, data: taskSeries, itemStyle: { color: '#7fa6d9' }, lineStyle: { color: '#7fa6d9' }, areaStyle: { color: 'rgba(127,166,217,0.16)' } }, |
|
|
|
|
|
{ name: '机加工完工', type: 'line', smooth: true, symbol: 'circle', symbolSize: 7, data: machiningSeries, itemStyle: { color: '#f5c15e' }, lineStyle: { color: '#f5c15e' }, areaStyle: { color: 'rgba(245,193,94,0.15)' } } |
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
name: '平均生产周期', |
|
|
|
|
|
type: 'bar', |
|
|
|
|
|
barWidth: 26, |
|
|
|
|
|
data: cycleCategoryList.map(item => item.value), |
|
|
|
|
|
itemStyle: { color: '#4f73bd' }, |
|
|
|
|
|
label: { |
|
|
|
|
|
normal: { |
|
|
|
|
|
show: true, |
|
|
|
|
|
position: 'top', |
|
|
|
|
|
distance: 6, |
|
|
|
|
|
color: '#d4ebff', |
|
|
|
|
|
fontSize: 20, |
|
|
|
|
|
formatter: ({ value }) => this.formatCycleLabel(value) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
] |
|
|
] |
|
|
}, true) |
|
|
}, true) |
|
|
}, |
|
|
}, |
|
|
countFinishByDay(list, day) { |
|
|
|
|
|
return list.filter(item => { |
|
|
|
|
|
const finishDate = this.toDayjs(item.finishDate) |
|
|
|
|
|
return finishDate && finishDate.format('YYYY-MM-DD') === day.format('YYYY-MM-DD') |
|
|
|
|
|
}).length |
|
|
|
|
|
|
|
|
buildAverageCycleCategoryList() { |
|
|
|
|
|
const wholeCategoryList = this.buildWholeLiftAverageCycleMap() |
|
|
|
|
|
const renovationAverageCycle = this.calcAverageCycleByList(this.renovationList) |
|
|
|
|
|
const machiningAverageCycle = this.calcAverageCycleByList(this.machiningList) |
|
|
|
|
|
return AVERAGE_CYCLE_CATEGORY_LIST.map((item) => { |
|
|
|
|
|
if (item.orderType === 'HOME_LIFT') { |
|
|
|
|
|
return { |
|
|
|
|
|
...item, |
|
|
|
|
|
value: this.formatCycleValue(wholeCategoryList[item.key]) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
if (item.key === 'VL2.5') { |
|
|
|
|
|
return { |
|
|
|
|
|
...item, |
|
|
|
|
|
value: this.formatCycleValue(renovationAverageCycle) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return { |
|
|
|
|
|
...item, |
|
|
|
|
|
value: this.formatCycleValue(machiningAverageCycle) |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
buildWholeLiftAverageCycleMap() { |
|
|
|
|
|
const sumMap = {} |
|
|
|
|
|
const countMap = {} |
|
|
|
|
|
AVERAGE_CYCLE_CATEGORY_LIST |
|
|
|
|
|
.filter(item => item.orderType === 'HOME_LIFT') |
|
|
|
|
|
.forEach(item => { |
|
|
|
|
|
sumMap[item.key] = 0 |
|
|
|
|
|
countMap[item.key] = 0 |
|
|
|
|
|
}) |
|
|
|
|
|
this.wholeList.forEach(item => { |
|
|
|
|
|
if (!item || item.status !== '已完成') return |
|
|
|
|
|
const categoryKey = this.resolveWholeLiftCategoryKey(item) |
|
|
|
|
|
if (!categoryKey || !Object.prototype.hasOwnProperty.call(sumMap, categoryKey)) return |
|
|
|
|
|
const cycleDays = this.resolveCycleDays(item) |
|
|
|
|
|
if (cycleDays <= 0) return |
|
|
|
|
|
sumMap[categoryKey] += cycleDays |
|
|
|
|
|
countMap[categoryKey] += 1 |
|
|
|
|
|
}) |
|
|
|
|
|
const avgMap = {} |
|
|
|
|
|
Object.keys(sumMap).forEach((key) => { |
|
|
|
|
|
avgMap[key] = countMap[key] > 0 ? sumMap[key] / countMap[key] : 0 |
|
|
|
|
|
}) |
|
|
|
|
|
return avgMap |
|
|
|
|
|
}, |
|
|
|
|
|
resolveWholeLiftCategoryKey(item) { |
|
|
|
|
|
const identity = this.normalizeModelIdentity( |
|
|
|
|
|
item && (item.modelNo || item.projectNo || item.taskNo || item.orderNo) |
|
|
|
|
|
) |
|
|
|
|
|
if (!identity) { |
|
|
|
|
|
return '' |
|
|
|
|
|
} |
|
|
|
|
|
if (identity.indexOf('VL3.0') === 0 || identity.indexOf('VL30') === 0) { |
|
|
|
|
|
return 'VL3.0' |
|
|
|
|
|
} |
|
|
|
|
|
if (identity.indexOf('VC2.0') === 0 || identity.indexOf('VC20') === 0) { |
|
|
|
|
|
return 'VC2.0' |
|
|
|
|
|
} |
|
|
|
|
|
if (identity.indexOf('Y18 2.0') === 0 || identity.indexOf('Y182.0') === 0 || identity.indexOf('Y1820') === 0) { |
|
|
|
|
|
return 'Y18 2.0' |
|
|
|
|
|
} |
|
|
|
|
|
if (identity.indexOf('Y18') === 0) { |
|
|
|
|
|
return 'Y18' |
|
|
|
|
|
} |
|
|
|
|
|
if (identity.indexOf('C') === 0) { |
|
|
|
|
|
return 'VC' |
|
|
|
|
|
} |
|
|
|
|
|
if (/^V\d+/.test(identity) || identity.indexOf('V') === 0) { |
|
|
|
|
|
return 'VW' |
|
|
|
|
|
} |
|
|
|
|
|
return '' |
|
|
|
|
|
}, |
|
|
|
|
|
normalizeModelIdentity(identityValue) { |
|
|
|
|
|
return String(identityValue || '') |
|
|
|
|
|
.toUpperCase() |
|
|
|
|
|
.replace(/[((].*?[))]/g, '') |
|
|
|
|
|
.replace(/[-_/]/g, ' ') |
|
|
|
|
|
.replace(/\s+/g, ' ') |
|
|
|
|
|
.trim() |
|
|
|
|
|
}, |
|
|
|
|
|
calcAverageCycleByList(list) { |
|
|
|
|
|
let sum = 0 |
|
|
|
|
|
let count = 0 |
|
|
|
|
|
;(list || []).forEach(item => { |
|
|
|
|
|
if (!item || item.status !== '已完成') return |
|
|
|
|
|
const cycleDays = this.resolveCycleDays(item) |
|
|
|
|
|
if (cycleDays <= 0) return |
|
|
|
|
|
sum += cycleDays |
|
|
|
|
|
count += 1 |
|
|
|
|
|
}) |
|
|
|
|
|
return count > 0 ? (sum / count) : 0 |
|
|
|
|
|
}, |
|
|
|
|
|
resolveCycleDays(item) { |
|
|
|
|
|
if (!item) return 0 |
|
|
|
|
|
const finishDate = this.toDayjs(item.finishDate || item.planDeliveryDate || item.planFinishDate) |
|
|
|
|
|
const startDate = this.toDayjs(item.createTime || item.planFinishDate || item.planDeliveryDate) |
|
|
|
|
|
if (!finishDate || !startDate) return 0 |
|
|
|
|
|
const diffDays = finishDate.diff(startDate, 'day', true) |
|
|
|
|
|
return diffDays > 0 ? diffDays : 0 |
|
|
|
|
|
}, |
|
|
|
|
|
formatCycleValue(value) { |
|
|
|
|
|
const numericValue = Number(value || 0) |
|
|
|
|
|
if (!Number.isFinite(numericValue) || numericValue <= 0) return 0 |
|
|
|
|
|
return Math.ceil(numericValue) |
|
|
|
|
|
}, |
|
|
|
|
|
formatCycleLabel(value) { |
|
|
|
|
|
const numericValue = Number(value || 0) |
|
|
|
|
|
if (!Number.isFinite(numericValue)) return '0' |
|
|
|
|
|
return String(Math.ceil(numericValue)) |
|
|
}, |
|
|
}, |
|
|
updateTime() { |
|
|
updateTime() { |
|
|
const now = new Date() |
|
|
const now = new Date() |
|
|
|