@@ -90,7 +90,7 @@ export default { | |||
}; | |||
function splitCurrentAndPrevious(factoryListResponse, targetListResponse) { | |||
// 初始数据 | |||
// 初始数据 | |||
const { chipInvest, ftoInvest, chipOutput, stdOutput, bipvOutput } = init(); | |||
if (factoryListResponse) { | |||
for (const factory of factoryListResponse) { | |||
@@ -150,7 +150,7 @@ function getFactoryTargetValue(targetList, factoryId) { | |||
} | |||
/** | |||
* | |||
* | |||
* @returns 初始化状态值 | |||
*/ | |||
function init() { | |||
@@ -254,10 +254,10 @@ async function doFetch(copilot_module = "yield", fetch_target, params) { | |||
} | |||
/** | |||
* | |||
* | |||
* @param {*} period 日周月年1,2,3,4 | |||
* @param {*} target 是否获取目标数据 | |||
* @returns | |||
* @returns | |||
*/ | |||
function getCopilotYield(period, target = false) { | |||
return getCopilotData("yield", period, target); | |||
@@ -0,0 +1,38 @@ | |||
<!-- | |||
filename: BipvOutput.vue | |||
author: liubin | |||
date: 2024-04-17 09:55:12 | |||
description: | |||
--> | |||
<template> | |||
<DoubleRingWrapperVue data-source="BIPV产出" :period="period" /> | |||
</template> | |||
<script> | |||
import DoubleRingWrapperVue from "./sub/ring/DoubleRingWrapper.vue"; | |||
export default { | |||
name: "BipvOutput", | |||
components: { DoubleRingWrapperVue }, | |||
props: { | |||
period: { | |||
type: String, | |||
default: "日", | |||
}, | |||
}, | |||
data() { | |||
return {}; | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
.flex-1 { | |||
flex: 1; | |||
} | |||
.stretch { | |||
align-self: stretch; | |||
} | |||
</style> |
@@ -0,0 +1,106 @@ | |||
<!-- | |||
filename: FtoInvest.vue | |||
author: liubin | |||
date: 2024-04-10 08:59:28 | |||
description: | |||
--> | |||
<template> | |||
<BarChartBase | |||
:legend="legend" | |||
:series="series" | |||
:xAxis="xAxis" | |||
in="ChipInvest" | |||
class="chip-invest-chart" | |||
/> | |||
</template> | |||
<script> | |||
import BarChartBase from "./sub/bar/BarChartBase.vue"; | |||
export default { | |||
name: "ChipInvest", | |||
components: { BarChartBase }, | |||
data() { | |||
// 城市数组的顺序必须是固定的 | |||
const cities = ["瑞昌", "邯郸", "株洲", "佳木斯", "成都", "凯盛", "蚌埠"]; | |||
return { | |||
xAxis: cities, | |||
}; | |||
}, | |||
props: { | |||
period: { | |||
type: String, | |||
default: "日", | |||
}, | |||
}, | |||
computed: { | |||
legend() { | |||
switch (this.period) { | |||
case "日": | |||
return [{ label: "昨日", color: "#12f7f1" }]; | |||
case "周": | |||
return [{ label: "本周", color: "#12f7f1" }]; | |||
case "月": { | |||
const year = new Date().getFullYear(); | |||
const month = new Date().getMonth() + 1; | |||
return [ | |||
{ label: `${year - 1}年${month}月`, color: "#12f7f1" }, | |||
{ label: `${year}年${month}月`, color: "#58adfa" }, | |||
]; | |||
} | |||
case "年": { | |||
const year = new Date().getFullYear(); | |||
return [ | |||
{ label: `${year - 1}年`, color: "#12f7f1" }, | |||
{ label: `${year}年`, color: "#58adfa" }, | |||
]; | |||
} | |||
default: | |||
return [ | |||
{ label: `${year - 1}年`, color: "#12f7f1" }, | |||
{ label: `${year}年`, color: "#58adfa" }, | |||
]; | |||
} | |||
}, | |||
series() { | |||
const { chipInvest } = this.$store.getters.copilot.yield; | |||
let dataList = null; | |||
switch (this.period) { | |||
case "日": | |||
case "周": | |||
dataList = chipInvest?.current; | |||
break; | |||
default: | |||
dataList = []; | |||
dataList[0] = chipInvest?.pervious; | |||
dataList[1] = chipInvest?.current; | |||
} | |||
return getTemplate(this.period, dataList); | |||
}, | |||
}, | |||
}; | |||
function getTemplate(period, dataList) { | |||
const year = new Date().getFullYear(); | |||
const month = new Date().getMonth() + 1; | |||
return period == "日" || period == "周" | |||
? [ | |||
{ | |||
name: period == "日" ? "昨日" : "本周", | |||
data: dataList ?? [], | |||
}, | |||
] | |||
: [ | |||
{ | |||
name: period == "年" ? `${year - 1}年` : `${year - 1}年${month}月`, | |||
data: dataList ? dataList[0] : [], | |||
}, | |||
{ | |||
name: period == "年" ? `${year}年` : `${year}年${month}月`, | |||
data: dataList ? dataList[1] : [], | |||
// : Array.from({ length: 7 }, () => Math.floor(Math.random() * 1000)), | |||
}, | |||
]; | |||
} | |||
</script> |
@@ -0,0 +1,38 @@ | |||
<!-- | |||
filename: ChipOutput.vue | |||
author: liubin | |||
date: 2024-04-17 09:55:12 | |||
description: | |||
--> | |||
<template> | |||
<DoubleRingWrapperVue data-source="芯片产出" :period="period" /> | |||
</template> | |||
<script> | |||
import DoubleRingWrapperVue from "./sub/ring/DoubleRingWrapper.vue"; | |||
export default { | |||
name: "ChipOutput", | |||
components: { DoubleRingWrapperVue }, | |||
props: { | |||
period: { | |||
type: String, | |||
default: "日", | |||
}, | |||
}, | |||
data() { | |||
return {}; | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
.flex-1 { | |||
flex: 1; | |||
} | |||
.stretch { | |||
align-self: stretch; | |||
} | |||
</style> |
@@ -0,0 +1,108 @@ | |||
<!-- | |||
filename: FtoInvest.vue | |||
author: liubin | |||
date: 2024-04-10 08:59:28 | |||
description: | |||
--> | |||
<template> | |||
<BarChartBase | |||
:legend="legend" | |||
:series="series" | |||
:xAxis="xAxis" | |||
in="ftoInvest" | |||
class="fto-chart" | |||
/> | |||
</template> | |||
<script> | |||
import BarChartBase from "./sub/bar/BarChartBase.vue"; | |||
export default { | |||
name: "FtoInvest", | |||
components: { BarChartBase }, | |||
data() { | |||
// 城市数组的顺序必须是固定的 | |||
const cities = ["瑞昌", "邯郸", "株洲", "佳木斯", "成都", "凯盛", "蚌埠"]; | |||
return { | |||
xAxis: cities, | |||
}; | |||
}, | |||
props: { | |||
period: { | |||
type: String, | |||
default: "日", | |||
}, | |||
}, | |||
computed: { | |||
legend() { | |||
switch (this.period) { | |||
case "日": | |||
return [{ label: "昨日", color: "#12f7f1" }]; | |||
case "周": | |||
return [{ label: "本周", color: "#12f7f1" }]; | |||
case "月": { | |||
const year = new Date().getFullYear(); | |||
const month = new Date().getMonth() + 1; | |||
return [ | |||
{ label: `${year - 1}年${month}月`, color: "#12f7f1" }, | |||
{ label: `${year}年${month}月`, color: "#58adfa" }, | |||
]; | |||
} | |||
case "年": { | |||
const year = new Date().getFullYear(); | |||
return [ | |||
{ label: `${year - 1}年`, color: "#12f7f1" }, | |||
{ label: `${year}年`, color: "#58adfa" }, | |||
]; | |||
} | |||
default: | |||
return [ | |||
{ label: `${year - 1}年`, color: "#12f7f1" }, | |||
{ label: `${year}年`, color: "#58adfa" }, | |||
]; | |||
} | |||
}, | |||
series() { | |||
const { ftoInvest } = this.$store.getters.copilot.yield; | |||
let dataList = null; | |||
switch (this.period) { | |||
case "日": | |||
case "周": | |||
dataList = ftoInvest?.current; | |||
break; | |||
default: | |||
dataList = []; | |||
dataList[0] = ftoInvest?.pervious; | |||
dataList[1] = ftoInvest?.current; | |||
} | |||
return getTemplate(this.period, dataList); | |||
}, | |||
}, | |||
}; | |||
function getTemplate(period, dataList) { | |||
const year = new Date().getFullYear(); | |||
const month = new Date().getMonth() + 1; | |||
return period == "日" || period == "周" | |||
? [ | |||
{ | |||
name: period == "日" ? "昨日" : "本周", | |||
data: dataList ?? [], | |||
}, | |||
] | |||
: [ | |||
{ | |||
name: period == "年" ? `${year - 1}年` : `${year - 1}年${month}月`, | |||
data: dataList ? dataList[0] : [], | |||
}, | |||
{ | |||
name: period == "年" ? `${year}年` : `${year}年${month}月`, | |||
data: dataList ? dataList[1] : [], | |||
// : Array.from({ length: 7 }, () => Math.floor(Math.random() * 1000)), | |||
}, | |||
]; | |||
} | |||
</script> |
@@ -0,0 +1,38 @@ | |||
<!-- | |||
filename: StdOutput.vue | |||
author: liubin | |||
date: 2024-04-17 09:55:12 | |||
description: | |||
--> | |||
<template> | |||
<DoubleRingWrapperVue data-source="标准组件产出" :period="period" /> | |||
</template> | |||
<script> | |||
import DoubleRingWrapperVue from "./sub/ring/DoubleRingWrapper.vue"; | |||
export default { | |||
name: "StdOutput", | |||
components: { DoubleRingWrapperVue }, | |||
props: { | |||
period: { | |||
type: String, | |||
default: "日", | |||
}, | |||
}, | |||
data() { | |||
return {}; | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
.flex-1 { | |||
flex: 1; | |||
} | |||
.stretch { | |||
align-self: stretch; | |||
} | |||
</style> |
@@ -0,0 +1,257 @@ | |||
<!-- | |||
filename: BarChartBase.vue | |||
author: liubin | |||
date: 2024-04-10 08:59:28 | |||
description: | |||
--> | |||
<template> | |||
<chart-container class="bar-chart-base"> | |||
<div class="legend"> | |||
<span | |||
v-for="item in legend" | |||
:key="item.label" | |||
class="legend-item" | |||
:style="{ fontSize: isFullscreen ? '0.58vw' : '0.54vw' }" | |||
>{{ item.label }}</span | |||
> | |||
</div> | |||
<div | |||
ref="chart" | |||
style="max-width: 50vw" | |||
:style="{ height: vHeight + 'vh' }" | |||
></div> | |||
</chart-container> | |||
</template> | |||
<script> | |||
import screenfull from "screenfull"; | |||
import ChartContainer from "../../../../components/ChartContainer.vue"; | |||
import chartMixin from "@/mixins/chart.js"; | |||
export default { | |||
name: "BarChartBase", | |||
components: { | |||
ChartContainer, | |||
}, | |||
mixins: [chartMixin], | |||
props: { | |||
vHeight: { | |||
type: Number, | |||
default: 34, | |||
}, | |||
legend: { | |||
type: Array, | |||
required: true, | |||
}, | |||
xAxis: { | |||
type: Array, | |||
required: true, | |||
}, | |||
series: { | |||
type: Array, | |||
required: true, | |||
}, | |||
in: { | |||
type: String, | |||
default: "", | |||
}, | |||
}, | |||
data() { | |||
return { | |||
isFullscreen: false, | |||
actualOptions: null, | |||
options: { | |||
grid: { | |||
left: "5%", | |||
right: "4%", | |||
bottom: "3%", | |||
top: "15%", | |||
containLabel: true, | |||
}, | |||
tooltip: {}, | |||
xAxis: { | |||
axisTick: { | |||
show: false, | |||
}, | |||
axisLine: { | |||
lineStyle: { | |||
color: "#4561AE", | |||
}, | |||
}, | |||
axisLabel: { | |||
color: "#fff", | |||
fontSize: 12, | |||
}, | |||
data: this.xAxis, | |||
}, | |||
yAxis: { | |||
name: "单位/片", | |||
nameTextStyle: { | |||
color: "#fff", | |||
fontSize: 12, | |||
}, | |||
axisTick: { | |||
show: false, | |||
}, | |||
axisLabel: { | |||
color: "#fff", | |||
fontSize: 12, | |||
}, | |||
axisLine: { | |||
show: true, | |||
lineStyle: { | |||
color: "#4561AE", | |||
}, | |||
}, | |||
splitLine: { | |||
lineStyle: { | |||
color: "#4561AE", | |||
}, | |||
}, | |||
}, | |||
series: [ | |||
{ | |||
name: "", // this.series[0].name, | |||
type: "bar", | |||
barWidth: 12, | |||
itemStyle: { | |||
borderRadius: [10, 10, 0, 0], | |||
color: { | |||
type: "linear", | |||
x: 0, | |||
y: 0, | |||
x2: 0, | |||
y2: 1, | |||
colorStops: [ | |||
{ | |||
offset: 0, | |||
color: "#12f7f1", // 0% 处的颜色 | |||
}, | |||
{ | |||
offset: 0.35, | |||
color: "#12f7f177", // 100% 处的颜色 | |||
}, | |||
{ | |||
offset: 0.75, | |||
color: "#12f7f133", // 100% 处的颜色 | |||
}, | |||
{ | |||
offset: 1, | |||
color: "transparent", // 100% 处的颜色 | |||
}, | |||
], | |||
global: false, // 缺省为 false | |||
}, | |||
}, | |||
data: [], // this.series[0].data, | |||
}, | |||
{ | |||
name: "", // this.series[1].name, | |||
type: "bar", | |||
barWidth: 12, | |||
// tooltip: { | |||
// valueFormatter: function (value) { | |||
// return value + " ml"; | |||
// }, | |||
// }, | |||
itemStyle: { | |||
borderRadius: [10, 10, 0, 0], | |||
color: { | |||
type: "linear", | |||
x: 0, | |||
y: 0, | |||
x2: 0, | |||
y2: 1, | |||
colorStops: [ | |||
{ | |||
offset: 0, | |||
color: "#57abf8", // 0% 处的颜色 | |||
}, | |||
{ | |||
offset: 1, | |||
color: "#364BFE66", // 100% 处的颜色 | |||
}, | |||
], | |||
global: false, // 缺省为 false | |||
}, | |||
}, | |||
data: [], // this.series[1].data, | |||
}, | |||
], | |||
}, | |||
}; | |||
}, | |||
watch: { | |||
/** 全屏状态切换时,对柱子粗细和字体大小进行相应调整 */ | |||
isFullscreen(val) { | |||
this.actualOptions.series.map((item) => { | |||
item.barWidth = val ? 18 : 12; | |||
}); | |||
this.actualOptions.xAxis.axisLabel.fontSize = val ? 18 : 12; | |||
this.actualOptions.yAxis.axisLabel.fontSize = val ? 18 : 12; | |||
this.actualOptions.yAxis.nameTextStyle.fontSize = val ? 18 : 12; | |||
this.initOptions(this.actualOptions); | |||
}, | |||
series(val) { | |||
if (!val) { | |||
this.initOptions(this.options); | |||
return; | |||
} | |||
const actualOptions = JSON.parse(JSON.stringify(this.options)); | |||
actualOptions.series[0].data = val[0].data; | |||
actualOptions.series[0].name = val[0].name; | |||
actualOptions.series[1].data = val?.[1]?.data || []; | |||
actualOptions.series[1].name = val?.[1]?.name || ""; | |||
this.actualOptions = actualOptions; | |||
this.initOptions(actualOptions); | |||
}, | |||
}, | |||
mounted() { | |||
this.actualOptions = this.options; | |||
this.initOptions(this.options); | |||
if (screenfull.isEnabled) { | |||
screenfull.on("change", () => { | |||
this.isFullscreen = screenfull.isFullscreen; | |||
}); | |||
} | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
.bar-chart-base { | |||
// position: relative; | |||
.legend { | |||
position: absolute; | |||
top: 5.2vh; | |||
right: 1vw; | |||
} | |||
.legend-item { | |||
position: relative; | |||
// font-size: 12px; | |||
margin-right: 0.67vw; | |||
&::before { | |||
content: ""; | |||
display: inline-block; | |||
width: 0.531vw; | |||
height: 0.531vw; | |||
background-color: #ccc; | |||
border-radius: 2px; | |||
margin-right: 0.22vw; | |||
} | |||
} | |||
.legend-item:nth-child(1):before { | |||
background-color: #12f7f1; | |||
} | |||
.legend-item:nth-child(2):before { | |||
background-color: #58adfa; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,149 @@ | |||
<!-- | |||
filename: CityData.vue | |||
author: liubin | |||
date: 2024-04-17 09:55:12 | |||
description: | |||
--> | |||
<template> | |||
<div class="city-data"> | |||
<div class="headquarter"> | |||
<div class="inner-shadow w-1"></div> | |||
<div class="inner-shadow flex-1 flex"> | |||
<CityName value="凯盛光伏" /> | |||
<CityValue :value="headquarterValue" horizontal :period="period" /> | |||
</div> | |||
<div class="inner-shadow w-1"></div> | |||
</div> | |||
<div class="city-item-container"> | |||
<CityItem | |||
v-for="city in cities" | |||
:key="city.name" | |||
:location="city.name" | |||
:value="city.value" | |||
:period="period" | |||
/> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import CityItemVue from "./CityItem.vue"; | |||
import CityNameVue from "./CityName.vue"; | |||
import CityValueVue from "./CityValue.vue"; | |||
export default { | |||
name: "CityData", | |||
components: { | |||
CityItem: CityItemVue, | |||
CityName: CityNameVue, | |||
CityValue: CityValueVue, | |||
}, | |||
props: { | |||
dataSource: { | |||
type: String, | |||
default: null, | |||
}, | |||
period: { | |||
type: String, | |||
default: "日", | |||
}, | |||
}, | |||
data() { | |||
return {}; | |||
}, | |||
computed: { | |||
headquarterValue() { | |||
let getterName = ""; | |||
switch (this.dataSource) { | |||
case "标准组件产出": | |||
getterName = "stdOutput"; | |||
break; | |||
case "芯片产出": | |||
getterName = "chipOutput"; | |||
break; | |||
case "BIPV产出": | |||
getterName = "bipvOutput"; | |||
break; | |||
} | |||
return ( | |||
"" + (this.$store.getters.copilot.yield[getterName]?.current?.[5] ?? 0) | |||
); | |||
}, | |||
cities() { | |||
let getterName = ""; | |||
switch (this.dataSource) { | |||
case "标准组件产出": | |||
getterName = "stdOutput"; | |||
break; | |||
case "芯片产出": | |||
getterName = "chipOutput"; | |||
break; | |||
case "BIPV产出": | |||
getterName = "bipvOutput"; | |||
break; | |||
} | |||
const _cities = [ | |||
{ name: "瑞昌", value: 0 }, | |||
{ name: "邯郸", value: 0 }, | |||
{ name: "株洲", value: 0 }, | |||
{ name: "佳木斯", value: 0 }, | |||
{ name: "成都", value: 0 }, | |||
{ name: "凯盛光伏", value: 0 }, | |||
{ name: "蚌埠兴科", value: 0 }, | |||
]; | |||
this.$store.getters.copilot.yield[getterName]?.current?.forEach( | |||
(v, idx) => { | |||
_cities[idx].value = v ?? 0; | |||
} | |||
); | |||
// 删掉凯盛光伏 | |||
_cities.splice(4, 1); | |||
return _cities; | |||
}, | |||
}, | |||
mounted() {}, | |||
methods: {}, | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
.city-data { | |||
width: 100%; | |||
flex: 1; | |||
display: flex; | |||
flex-direction: column; | |||
gap: 8px; | |||
} | |||
.headquarter { | |||
flex: 1; | |||
display: flex; | |||
gap: 8px; | |||
} | |||
.w-1 { | |||
width: 70px; | |||
} | |||
.flex { | |||
display: flex; | |||
} | |||
.flex-1 { | |||
flex: 1; | |||
} | |||
.inner-shadow { | |||
box-shadow: inset 0 0 12px 2px #fff3; | |||
border-radius: 4px; | |||
} | |||
.city-item-container { | |||
flex: 3; | |||
display: grid; | |||
grid-template-columns: repeat(2, 1fr); | |||
gap: 8px; | |||
} | |||
</style> |
@@ -0,0 +1,59 @@ | |||
<!-- | |||
filename: CityItem.vue | |||
author: liubin | |||
date: 2024-04-17 09:55:12 | |||
description: | |||
--> | |||
<template> | |||
<div class="city-item inner-shadow"> | |||
<CityName :value="location" /> | |||
<CityValue :value="value + ''" :period="period" /> | |||
</div> | |||
</template> | |||
<script> | |||
import CityNameVue from "./CityName.vue"; | |||
import CityValueVue from "./CityValue.vue"; | |||
import GradientTextVue from "../gradient/GradientText.vue"; | |||
export default { | |||
name: "CityItem", | |||
components: { | |||
GradientTextVue, | |||
CityName: CityNameVue, | |||
CityValue: CityValueVue, | |||
}, | |||
props: { | |||
location: { | |||
type: String, | |||
default: "", | |||
}, | |||
value: { | |||
type: Number, | |||
default: 0, | |||
}, | |||
period: { | |||
type: String, | |||
default: "日", | |||
}, | |||
}, | |||
data() { | |||
return {}; | |||
}, | |||
computed: {}, | |||
mounted() {}, | |||
methods: {}, | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
.city-item { | |||
display: flex; | |||
} | |||
.inner-shadow { | |||
box-shadow: inset 0 0 12px 2px #fff3; | |||
border-radius: 4px; | |||
} | |||
</style> |
@@ -0,0 +1,55 @@ | |||
<!-- | |||
filename: CityName.vue | |||
author: liubin | |||
date: 2024-04-10 08:59:28 | |||
description: | |||
--> | |||
<template> | |||
<div class="city-name"> | |||
<img :src="Icon" alt="city icon" /> | |||
<span>{{ value }}</span> | |||
</div> | |||
</template> | |||
<script> | |||
import Icon from "../../../assets/icon.png"; | |||
export default { | |||
name: "CityName", | |||
props: { | |||
value: { | |||
type: String, | |||
default: "", | |||
}, | |||
}, | |||
data() { | |||
return { Icon }; | |||
}, | |||
}; | |||
</script> | |||
<style scoped> | |||
.city-name { | |||
min-width: 80px; | |||
margin: auto; | |||
display: flex; | |||
flex-direction: column; | |||
align-items: center; | |||
justify-content: center; | |||
padding: 0 8px; | |||
flex: 1; | |||
} | |||
img { | |||
/* width: 32px; */ | |||
width: 1.543vw; | |||
} | |||
span { | |||
/* font-size: 12px; */ | |||
font-size: 0.77vw; | |||
letter-spacing: 2px; | |||
text-align: center; | |||
} | |||
</style> |
@@ -0,0 +1,127 @@ | |||
<!-- | |||
filename: CityValue.vue | |||
author: liubin | |||
date: 2024-04-10 08:59:28 | |||
description: | |||
--> | |||
<template> | |||
<div class="city-value" :class="[horizontal ? 'horizontal' : '']"> | |||
<span class="hint" :class="[horizontal ? 'horizontal' : '']">{{ | |||
period == "周" ? "本周产出" : "今日产出" | |||
}}</span> | |||
<span class="value" :class="[horizontal ? 'horizontal' : '']">{{ | |||
value | numberFilter | |||
}}</span> | |||
<!-- <GradientTextVue :text="value" :size="horizontal ? 32 : 26" /> --> | |||
</div> | |||
</template> | |||
<script> | |||
import GradientTextVue from "../gradient/GradientText.vue"; | |||
export default { | |||
name: "CityValue", | |||
components: { GradientTextVue }, | |||
props: { | |||
period: { | |||
type: String, | |||
default: "日", | |||
}, | |||
value: { | |||
type: String, | |||
default: "", | |||
}, | |||
horizontal: { | |||
type: Boolean, | |||
default: false, | |||
}, | |||
}, | |||
filters: { | |||
numberFilter(value) { | |||
if (value != null && !isNaN(parseInt(value))) { | |||
return parseInt(value).toLocaleString(); | |||
} else { | |||
return value; | |||
} | |||
}, | |||
}, | |||
data() { | |||
return {}; | |||
}, | |||
}; | |||
</script> | |||
<style scoped> | |||
.city-value { | |||
display: flex; | |||
flex-direction: column; | |||
align-items: center; | |||
justify-content: center; | |||
padding: 0 8px; | |||
flex: 2; | |||
position: relative; | |||
} | |||
.city-value.horizontal { | |||
flex-direction: row; | |||
} | |||
.city-value::after { | |||
content: ""; | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
width: 1px; | |||
height: 100%; | |||
background: linear-gradient( | |||
to bottom, | |||
transparent 20%, | |||
#fff 50%, | |||
transparent 80% | |||
); | |||
} | |||
span.hint { | |||
margin: 0 0.77vw; | |||
font-size: 0.77vw; | |||
order: 2; | |||
/* margin: 0 12px; | |||
width: 32px; | |||
font-size: 12px; */ | |||
} | |||
span.hint.horizontal { | |||
margin: 0 1.235vw; | |||
width: 1.543vw; | |||
order: 1; | |||
font-size: 0.77vw; | |||
/* margin: 0 12px; | |||
width: 32px; | |||
font-size: 12px; */ | |||
} | |||
.value { | |||
color: #4dd2fe; | |||
text-align: center; | |||
font-size: 1.132vw; | |||
order: 1; | |||
} | |||
.value.horizontal { | |||
text-align: left; | |||
flex: 1; | |||
font-size: 1.543vw; | |||
order: 2; | |||
} | |||
svg, | |||
.value { | |||
width: 100px; | |||
order: 1; | |||
} | |||
.value.horizontal, | |||
svg.horizontal { | |||
order: 2; | |||
} | |||
</style> |
@@ -0,0 +1,64 @@ | |||
<!-- | |||
filename: GradientText.vue | |||
author: liubin | |||
date: 2024-04-24 16:33:25 | |||
description: | |||
--> | |||
<template> | |||
<svg :height="size + 8" width="100%"> | |||
<defs> | |||
<linearGradient id="smoke-text" x1="0%" y1="0%" x2="0%" y2="100%"> | |||
<stop offset="0%" style="stop-color: #00fff4; stop-opacity: 1" /> | |||
<stop offset="100%" style="stop-color: #37bdfe; stop-opacity: 1" /> | |||
</linearGradient> | |||
</defs> | |||
<text | |||
x="0" | |||
:y="size" | |||
fill="url(#smoke-text)" | |||
:style="{ | |||
fontSize: `${size}px`, | |||
letterSpacing: spacing || '2px', | |||
fontFamily: 'Calibri, Verdana, sans-serif', | |||
}" | |||
> | |||
{{ text | numberFilter }} | |||
</text> | |||
</svg> | |||
</template> | |||
<script> | |||
export default { | |||
name: "GradientText", | |||
components: {}, | |||
props: { | |||
text: { | |||
type: String, | |||
default: "Test", | |||
}, | |||
spacing: { | |||
type: String, | |||
default: "1px", | |||
}, | |||
size: { | |||
type: Number, | |||
default: 24, | |||
}, | |||
}, | |||
filters: { | |||
numberFilter(value) { | |||
if (value != null && !isNaN(parseInt(value))) { | |||
return parseInt(value).toLocaleString(); | |||
} else { | |||
return value; | |||
} | |||
}, | |||
}, | |||
data() { | |||
return {}; | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="scss"></style> |
@@ -0,0 +1,221 @@ | |||
<!-- | |||
filename: DoubleRingChart.vue | |||
author: liubin | |||
date: 2024-04-17 11:01:55 | |||
description: | |||
--> | |||
<template> | |||
<div class="double-ring-chart"> | |||
<div ref="chart" class="double-ring-chart__container"></div> | |||
<!-- style="{ height: vHeight + 'vh' }" --> | |||
<div class="double-ring-chart__legend"> | |||
<div v-for="item in legendItems" :key="item.label" class="legend-item"> | |||
<span class="legend-item__label">{{ item.label }}</span> | |||
<span class="legend-item__value">{{ item.value | numberFilter }}</span> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import chartMixin from "@/mixins/chart.js"; | |||
import fullscreenMixin from "@/mixins/fullscreen.js"; | |||
import getOptions from "../../../options/double-ring-chart-options"; | |||
export default { | |||
name: "DoubleRingChart", | |||
mixins: [chartMixin, fullscreenMixin], | |||
props: { | |||
vHeight: { | |||
type: Number, | |||
default: 24, | |||
}, | |||
factoryId: { | |||
type: Number, | |||
required: true, | |||
}, | |||
period: { | |||
type: String, | |||
default: "日", | |||
}, | |||
dataSource: { | |||
type: String, | |||
default: null, | |||
}, | |||
}, | |||
data() { | |||
return {}; | |||
}, | |||
filters: { | |||
numberFilter(val) { | |||
if (!isNaN(val)) { | |||
return (+val).toLocaleString(); | |||
} | |||
return 0; | |||
}, | |||
}, | |||
computed: { | |||
dataSourceField() { | |||
switch (this.dataSource) { | |||
case "标准组件产出": | |||
return "stdOutput"; | |||
case "芯片产出": | |||
return "chipOutput"; | |||
case "BIPV产出": | |||
return "bipvOutput"; | |||
} | |||
}, | |||
valueTuple() { | |||
// [previousValue, currentValue, sumValue?] | |||
const getter = this.$store.getters.copilot.yield[this.dataSourceField]; | |||
if (this.period === "日" || this.period === "周") { | |||
return [ | |||
getter.previous[this.factoryId], | |||
getter.current[this.factoryId], | |||
]; | |||
} | |||
// [100, 200, 200] | |||
return [ | |||
getter.previous[this.factoryId], | |||
getter.current[this.factoryId], | |||
getter.target[this.factoryId], | |||
]; | |||
}, | |||
options() { | |||
const year = new Date().getFullYear(); | |||
const month = new Date().getMonth() + 1; | |||
const vt = this.valueTuple; | |||
let titleValue = | |||
vt[0] != null && vt[2] != null && vt[2] !== 0 | |||
? `${vt[1] / vt[2]}%` | |||
: "0%", | |||
subtitle = | |||
this.period == "月" ? `${month}月累计产出` : `${year}年累计产出`; | |||
return getOptions({ | |||
titleValue, | |||
subtitle, | |||
previousSum: this.valueTuple[0], | |||
currentSum: this.valueTuple[1], | |||
targetSum: this.valueTuple[2], | |||
}); | |||
}, | |||
legendItems() { | |||
return calculateItems(this.period, this.valueTuple); | |||
}, | |||
}, | |||
watch: { | |||
legendItems() { | |||
this.initOptions(this.options); | |||
}, | |||
}, | |||
mounted() { | |||
this.initOptions(this.options); | |||
}, | |||
methods: { | |||
// fullscreen mixin 需要的回调 | |||
fullscreenCallback(isFullscreen) { | |||
console.log("isFullscreen--->", isFullscreen); | |||
}, | |||
}, | |||
}; | |||
function calculateItems(period, valueTuple) { | |||
let items = []; | |||
const today = new Date().getDate(); | |||
const month = new Date().getMonth() + 1; | |||
const year = new Date().getFullYear(); | |||
switch (period) { | |||
case "日": | |||
items = [ | |||
{ label: `${month}月${today}日累计`, value: valueTuple[1] }, | |||
{ label: `去年${month}月${today}日累计`, value: valueTuple[0] }, | |||
]; | |||
break; | |||
case "周": | |||
items = [ | |||
{ label: `本周累计`, value: valueTuple[1] }, | |||
{ label: `去年本周累计`, value: valueTuple[0] }, | |||
]; | |||
break; | |||
case "月": | |||
items = [ | |||
{ label: `${month}月累计`, value: valueTuple[1] }, | |||
{ label: `去年${month}月累计`, value: valueTuple[0] }, | |||
{ label: `${month}月目标`, value: valueTuple[2] }, | |||
]; | |||
break; | |||
case "年": | |||
items = [ | |||
{ label: `${year}年累计`, value: valueTuple[1] }, | |||
{ label: `${year - 1}年累计`, value: valueTuple[0] }, | |||
{ label: `${year}年目标`, value: valueTuple[2] }, | |||
]; | |||
break; | |||
} | |||
return items; | |||
} | |||
</script> | |||
<style scoped> | |||
.double-ring-chart { | |||
height: 100%; | |||
display: flex; | |||
flex-direction: column; | |||
} | |||
.double-ring-chart__container { | |||
flex: 1; | |||
height: 0; | |||
} | |||
.double-ring-chart__legend { | |||
padding: 12px; | |||
color: #fff; | |||
display: flex; | |||
justify-content: center; | |||
gap: 32px; | |||
} | |||
.legend-item { | |||
display: flex; | |||
flex-direction: column; | |||
align-items: flex-start; | |||
} | |||
.legend-item__label { | |||
position: relative; | |||
} | |||
.legend-item__label::before { | |||
content: ""; | |||
position: absolute; | |||
width: 12px; | |||
height: 12px; | |||
background: #ccc; | |||
border-radius: 2px; | |||
top: 6px; | |||
left: -18px; | |||
} | |||
.legend-item:nth-child(1) .legend-item__label::before { | |||
background: #12fff5; | |||
} | |||
.legend-item:nth-child(1) .legend-item__value { | |||
color: #12fff5; | |||
} | |||
.legend-item:nth-child(2) .legend-item__label::before { | |||
background: #0f65ff; | |||
} | |||
.legend-item:nth-child(2) .legend-item__value { | |||
color: #0f65ff; | |||
} | |||
.legend-item:nth-child(3) .legend-item__label::before { | |||
background: #003982; | |||
} | |||
</style> |
@@ -0,0 +1,86 @@ | |||
<!-- | |||
filename: DoubleRingWrapper.vue | |||
author: liubin | |||
date: 2024-04-17 09:55:12 | |||
description: | |||
--> | |||
<template> | |||
<div class="double-ring-wrapper"> | |||
<template v-if="period == '月' || period == '年'"> | |||
<copilot-select | |||
@update:active="handleActiveUpdate" | |||
:options="cityOptions" | |||
/> | |||
<div class="flex-1 stretch"> | |||
<DoubleRingChartVue | |||
:data-source="dataSource" | |||
:period="period" | |||
:factoryId="factoryId" | |||
/> | |||
</div> | |||
</template> | |||
<template v-else> | |||
<CityData :data-source="dataSource" :period="period" /> | |||
</template> | |||
</div> | |||
</template> | |||
<script> | |||
import CopilotSelect from "@/views/copilot/components/select.vue"; | |||
import DoubleRingChartVue from "./DoubleRingChart.vue"; | |||
import CityData from "../city/CityData.vue"; | |||
export default { | |||
name: "DoubleRingWrapper", | |||
components: { CopilotSelect, DoubleRingChartVue, CityData }, | |||
props: { | |||
dataSource: { | |||
type: String, | |||
default: null, | |||
}, | |||
period: { | |||
type: String, | |||
default: "日", | |||
}, | |||
}, | |||
data() { | |||
return { | |||
factoryId: 4, // 默认成都 | |||
cityOptions: [ | |||
"成都", | |||
"邯郸", | |||
"株洲", | |||
"瑞昌", | |||
"佳木斯", | |||
"凯盛光伏", | |||
"蚌埠兴科", | |||
], | |||
}; | |||
}, | |||
methods: { | |||
handleActiveUpdate(index) { | |||
this.factoryId = index; | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
.double-ring-wrapper { | |||
height: 100%; | |||
padding: 12px 24px; | |||
display: flex; | |||
gap: 12px; | |||
flex-direction: column; | |||
align-items: center; | |||
} | |||
.flex-1 { | |||
flex: 1; | |||
} | |||
.stretch { | |||
align-self: stretch; | |||
} | |||
</style> |
@@ -1,13 +1,16 @@ | |||
<!-- | |||
filename: index.vue | |||
author: liubin | |||
date: 2024-04-16 14:40:15 | |||
description: 效率驾驶舱 | |||
<!-- | |||
* @Author: zhp | |||
* @Date: 2024-05-07 10:04:53 | |||
* @LastEditTime: 2024-05-07 10:20:17 | |||
* @LastEditors: zhp | |||
* @Description: | |||
--> | |||
<template> | |||
<div class="efficiency-copilot"> | |||
<Container title="芯片良率" icon="chip2"></Container> | |||
<Container title="芯片良率" icon="chip2"> | |||
<std-output :period="period" /> | |||
</Container> | |||
<Container title="标准组件良率" icon="std"></Container> | |||
<Container title="芯片OEE" icon="chip"></Container> | |||
<Container title="转化效率" icon="cube"></Container> | |||
@@ -16,9 +19,11 @@ | |||
<script> | |||
import Container from "@/views/copilot/components/Container.vue"; | |||
import StdOutput from "./components/StdOutput.vue"; | |||
export default { | |||
name: "EfficiencyCopilot", | |||
components: { Container }, | |||
components: { Container, StdOutput }, | |||
props: { | |||
period: { | |||
type: String, | |||
@@ -0,0 +1,145 @@ | |||
export default ({ | |||
titleValue, | |||
subtitle, | |||
previousSum, | |||
currentSum, | |||
targetSum, | |||
}) => ({ | |||
grid: { | |||
left: 0, | |||
right: 0, | |||
bottom: 0, | |||
top: 0, | |||
containLabel: true, | |||
}, | |||
tooltip: {}, | |||
title: { | |||
text: titleValue, | |||
left: "49%", | |||
top: "39%", | |||
textAlign: "center", | |||
textStyle: { | |||
fontWeight: 600, | |||
fontSize: 32, | |||
color: "#fffd", | |||
}, | |||
subtext: `\u2002${subtitle}\u2002`, | |||
subtextStyle: { | |||
fontSize: 14, | |||
fontWeight: 100, | |||
color: "#fffd", | |||
align: "right", | |||
}, | |||
}, | |||
series: [ | |||
// 背景 series | |||
{ | |||
type: "pie", | |||
name: "当前目标", | |||
radius: ["70%", "85%"], | |||
center: ["50%", "52%"], | |||
emptyCircleStyle: { | |||
color: "#042c5f33", | |||
}, | |||
}, | |||
// 数据 series | |||
{ | |||
type: "pie", | |||
radius: ["70%", "85%"], | |||
center: ["50%", "52%"], | |||
avoidLabelOvervlap: false, | |||
label: { | |||
show: false, | |||
}, | |||
labelLine: { | |||
show: false, | |||
}, | |||
data: [ | |||
{ | |||
value: currentSum, | |||
name: "当前累计产出", | |||
selected: false, | |||
itemStyle: { | |||
borderJoin: "round", | |||
borderCap: "round", | |||
borderWidth: 12, | |||
borderRadius: "50%", | |||
color: { | |||
type: "linear", | |||
x: 1, | |||
y: 0, | |||
x2: 0, | |||
y2: 1, | |||
colorStops: [ | |||
{ offset: 0, color: "#4CF0E811" }, | |||
{ offset: 1, color: "#4CF0E8" }, | |||
], | |||
}, | |||
}, | |||
}, | |||
{ | |||
value: | |||
targetSum > currentSum | |||
? targetSum - currentSum | |||
: targetSum == 0 | |||
? currentSum == 0 | |||
? 1 | |||
: 0 | |||
: 0, | |||
name: "未达成累计", | |||
itemStyle: { color: "transparent" }, | |||
label: { show: false }, | |||
}, | |||
], | |||
}, | |||
// 数据 series2 - 2023累计 | |||
{ | |||
type: "pie", | |||
radius: ["55%", "70%"], | |||
center: ["50%", "52%"], | |||
avoidLabelOvervlap: false, | |||
label: { | |||
show: false, | |||
}, | |||
labelLine: { | |||
show: false, | |||
}, | |||
data: [ | |||
{ | |||
value: previousSum, | |||
name: "上期累计产出", | |||
selected: false, | |||
itemStyle: { | |||
borderJoin: "round", | |||
borderCap: "round", | |||
borderWidth: 12, | |||
borderRadius: "50%", | |||
color: { | |||
type: "linear", | |||
x: 1, | |||
y: 0, | |||
x2: 0, | |||
y2: 1, | |||
colorStops: [ | |||
{ offset: 0, color: "#1065ff66" }, | |||
{ offset: 1, color: "#1065ff" }, | |||
], | |||
}, | |||
}, | |||
}, | |||
{ | |||
value: | |||
targetSum > previousSum | |||
? targetSum - previousSum | |||
: previousSum == 0 | |||
? 1 | |||
: 0, | |||
name: "-", | |||
itemStyle: { color: "transparent" }, | |||
label: { show: false }, | |||
}, | |||
], | |||
}, | |||
], | |||
}); |