31 changed files with 721 additions and 201 deletions
After Width: | Height: | Size: 356 KiB |
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 398 KiB |
Before Width: | Height: | Size: 29 KiB |
@ -0,0 +1,164 @@
@@ -0,0 +1,164 @@
|
||||
<template> |
||||
<v-chart |
||||
class="chart" |
||||
:option="chartOption" |
||||
autoresize |
||||
:loading-options="loadingOpt" |
||||
:loading="loading" |
||||
ref="chartRef" |
||||
/> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import dayjs from 'dayjs' |
||||
import VChart from 'vue-echarts' |
||||
import { use } from 'echarts/core' |
||||
import { CanvasRenderer } from 'echarts/renderers' |
||||
import type { EChartsOption, SeriesOption } from 'echarts/types/dist/shared.js' |
||||
import { LineChart } from 'echarts/charts' |
||||
import { |
||||
TooltipComponent, |
||||
LegendComponent, |
||||
DataZoomComponent, |
||||
GridComponent, |
||||
} from 'echarts/components' |
||||
use([ |
||||
CanvasRenderer, |
||||
TooltipComponent, |
||||
LineChart, |
||||
LegendComponent, |
||||
GridComponent, |
||||
DataZoomComponent, |
||||
]) |
||||
|
||||
const props = defineProps({ |
||||
data: { |
||||
type: Array as PropType<Array<{ time: string; storage: number; outStorage: number }>>, |
||||
default: () => [], |
||||
}, |
||||
filterType: { |
||||
type: Number as PropType<Number>, |
||||
default: 1, |
||||
}, |
||||
}) |
||||
|
||||
const loading = ref(true) |
||||
const loadingOpt = { |
||||
type: 'default', |
||||
text: '暂无数据', |
||||
color: '#c23531', |
||||
textColor: '#666', |
||||
maskColor: 'rgba(255, 255, 255, 0)', |
||||
showSpinner: false, |
||||
} |
||||
|
||||
const chartRef = ref() |
||||
const chartOption = computed<EChartsOption>(() => { |
||||
loading.value = false |
||||
|
||||
const option: EChartsOption = { |
||||
grid: { |
||||
left: 50, |
||||
right: 10, |
||||
top: '8%', |
||||
bottom: 50, |
||||
}, |
||||
tooltip: { |
||||
trigger: 'axis', |
||||
confine: true, |
||||
appendToBody: true, |
||||
}, |
||||
legend: { |
||||
type: 'scroll', |
||||
formatter: name => { |
||||
return name.length > 6 ? name.substring(0, 6) + '...' : name |
||||
}, |
||||
tooltip: { |
||||
show: true, |
||||
}, |
||||
right: 0, |
||||
itemWidth: 10, |
||||
itemHeight: 10, |
||||
pageIconSize: 12, |
||||
// data: data, |
||||
}, |
||||
xAxis: { |
||||
type: 'category', |
||||
axisLine: { |
||||
show: true, |
||||
lineStyle: { |
||||
width: 1, |
||||
type: 'solid', |
||||
}, |
||||
}, |
||||
axisLabel: { |
||||
fontSize: 12, |
||||
}, |
||||
axisTick: { |
||||
show: false, |
||||
}, |
||||
data: props.data.map(r => r.time), |
||||
}, |
||||
yAxis: { |
||||
type: 'value', |
||||
splitNumber: 6, |
||||
axisLine: { |
||||
//y轴线的颜色以及宽度 |
||||
show: true, |
||||
lineStyle: { |
||||
width: 1, |
||||
type: 'solid', |
||||
}, |
||||
}, |
||||
axisLabel: { |
||||
margin: 12, |
||||
fontSize: 12, |
||||
}, |
||||
splitLine: { |
||||
show: true, |
||||
lineStyle: {}, |
||||
}, |
||||
}, |
||||
|
||||
series: [ |
||||
{ |
||||
name: '入库设备数', |
||||
type: 'line', |
||||
data: props.data.map(r => r.storage ?? 0), |
||||
smooth: true, |
||||
symbol: 'circle', |
||||
symbolSize: 8, |
||||
lineStyle: { |
||||
width: 2, |
||||
}, |
||||
itemStyle: { |
||||
color: '#619925', |
||||
}, |
||||
}, |
||||
{ |
||||
name: '出库设备数', |
||||
type: 'line', |
||||
data: props.data.map((r: any) => r.outStorage ?? 0), |
||||
smooth: true, |
||||
symbol: 'circle', |
||||
symbolSize: 8, |
||||
lineStyle: { |
||||
width: 2, |
||||
}, |
||||
itemStyle: { |
||||
color: '#FFA500', |
||||
}, |
||||
}, |
||||
], |
||||
} |
||||
|
||||
return option |
||||
}) |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.chart { |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
</style> |
@ -0,0 +1,124 @@
@@ -0,0 +1,124 @@
|
||||
<template> |
||||
<v-chart class="chart" :option="option" :autoresize="autoresize" /> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import VChart from 'vue-echarts' |
||||
import { use } from 'echarts/core' |
||||
import { CanvasRenderer } from 'echarts/renderers' |
||||
import { PieChart } from 'echarts/charts' |
||||
import { |
||||
TitleComponent, |
||||
TooltipComponent, |
||||
LegendComponent, |
||||
GraphicComponent, |
||||
} from 'echarts/components' |
||||
import type { EChartsOption } from 'echarts' |
||||
import { useTheme } from '@/utils/useTheme' |
||||
use([ |
||||
CanvasRenderer, |
||||
PieChart, |
||||
TitleComponent, |
||||
TooltipComponent, |
||||
GraphicComponent, |
||||
LegendComponent, |
||||
]) |
||||
|
||||
interface Props { |
||||
data: any[] |
||||
} |
||||
const { chartGraphicTextColor } = useTheme() |
||||
|
||||
const props = withDefaults(defineProps<Props>(), { |
||||
data: () => [], |
||||
}) |
||||
|
||||
const updateTrigger = ref(0) |
||||
const autoresize = { |
||||
throttle: 2500, |
||||
onResize: () => { |
||||
updateTrigger.value++ |
||||
}, |
||||
} |
||||
|
||||
// 数据总数 |
||||
const total = computed(() => props.data.reduce((acc, cur) => acc + cur.value, 0)) |
||||
|
||||
const option = computed(() => { |
||||
const trigger = updateTrigger.value |
||||
const res: EChartsOption = { |
||||
tooltip: { |
||||
show: false, |
||||
}, |
||||
graphic: { |
||||
elements: [ |
||||
{ |
||||
type: 'text', |
||||
left: 'center', |
||||
top: '36%', |
||||
style: { |
||||
text: total.value.toString(), |
||||
fill: chartGraphicTextColor.value, |
||||
fontSize: 24, |
||||
fontWeight: 700, |
||||
}, |
||||
}, |
||||
{ |
||||
type: 'text', |
||||
left: 'center', |
||||
top: '42%', |
||||
style: { |
||||
text: '设备总数', |
||||
fill: chartGraphicTextColor.value, |
||||
fontSize: 20, |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
legend: { |
||||
show: true, |
||||
bottom: '3%', |
||||
itemWidth: 20, |
||||
itemHeight: 15, |
||||
itemGap: 10, |
||||
textStyle: { |
||||
fontSize: 16, |
||||
}, |
||||
}, |
||||
color: ['#4CAF50', '#2196F3', '#FBB852', '#FF9800', '#F44336', '#ccc'], |
||||
series: [ |
||||
{ |
||||
type: 'pie', |
||||
radius: ['40%', '55%'], |
||||
center: ['50%', '40%'], |
||||
data: props.data, |
||||
avoidLabelOverlap: false, |
||||
emphasis: { |
||||
itemStyle: { |
||||
shadowBlur: 10, |
||||
shadowOffsetX: 1, |
||||
shadowColor: 'rgba(0, 0, 0, 0.5)', |
||||
}, |
||||
}, |
||||
label: { |
||||
show: true, |
||||
position: 'outside', |
||||
// formatter: '{b}: {c} ({d}%)', |
||||
formatter: ' {c}', |
||||
}, |
||||
labelLine: { |
||||
show: true, |
||||
}, |
||||
}, |
||||
], |
||||
} |
||||
return res |
||||
}) |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
.chart { |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
</style> |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
|
||||
export const data = [ |
||||
{time: 1733011200, storage: 188, outStorage: 1407}, |
||||
{time: 1733097600, storage: 1139, outStorage: 1262}, |
||||
{time: 1733184000, storage: 733, outStorage: 1498}, |
||||
{time: 1733270400, storage: 555, outStorage: 376}, |
||||
{time: 1733356800, storage: 617, outStorage: 791}, |
||||
{time: 1733443200, storage: 119, outStorage: 418}, |
||||
{time: 1733529600, storage: 169, outStorage: 1439}, |
||||
{time: 1733616000, storage: 972, outStorage: 348}, |
||||
{time: 1733702400, storage: 1087, outStorage: 631}, |
||||
{time: 1733788800, storage: 229, outStorage: 287}, |
||||
{time: 1733875200, storage: 472, outStorage: 195}, |
||||
{time: 1733961600, storage: 790, outStorage: 478}, |
||||
] |
@ -0,0 +1,276 @@
@@ -0,0 +1,276 @@
|
||||
<template> |
||||
<div class="device-home"> |
||||
<header class="device-status-blok"> |
||||
<div |
||||
v-for="(item, idx) in summaryList" |
||||
:key="idx" |
||||
class="val-blok" |
||||
:style="{ backgroundColor: item.color }" |
||||
> |
||||
<div class="val"> |
||||
<span>{{ item.label }}</span> |
||||
<span class="num">{{ item.value }}</span> |
||||
</div> |
||||
<Icon :icon="item.icon" :size="48" /> |
||||
<!-- <img :src="item.icon" alt="" /> --> |
||||
</div> |
||||
</header> |
||||
<main> |
||||
<EdfsWrap title="库存数据" class="line"> |
||||
<template #title-right> |
||||
<el-button-group size="small"> |
||||
<template v-for="{ key, name } in filterData" :key="key"> |
||||
<EdfsButton |
||||
:inner-text="name" |
||||
@click="onTimeSearch(key)" |
||||
:type="currentFilter === key ? 'success' : ''" |
||||
/> |
||||
</template> |
||||
</el-button-group> |
||||
</template> |
||||
<LineChart :data="lineChartData" :filterType="currentFilter" /> |
||||
</EdfsWrap> |
||||
<EdfsWrap title="设备数据" class="pie"> |
||||
<PieChart :data="pieData" /> |
||||
</EdfsWrap> |
||||
</main> |
||||
<footer> |
||||
<EdfsWrap title="快捷入口" class="shortcut"> |
||||
<div class="shortcut-list"> |
||||
<template v-for="(item, idx) in shortcutList" :key="idx"> |
||||
<div class="item" @click="onShortcutClick(item)"> |
||||
<el-button :type="item.type as any" circle size="large"> |
||||
<Icon :icon="item.icon" :size="24" /> |
||||
</el-button> |
||||
<span>{{ item.title }}</span> |
||||
</div> |
||||
</template> |
||||
</div> |
||||
</EdfsWrap> |
||||
</footer> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import EdfsWrap from '@/components/dashboard/Edfs-wrap.vue' |
||||
import { data } from './components/utils' |
||||
import LineChart from './components/line-chart.vue' |
||||
import PieChart from './components/pie-chart.vue' |
||||
import { Icon } from '@/components/dashboard/Icon/index' |
||||
import { useRouter } from 'vue-router' |
||||
import { |
||||
getDeviceStorageAndMaintainChart, |
||||
getDeviceSummary, |
||||
getDeviceSummaryByStatus, |
||||
} from '@/api/module/eam/device' |
||||
import dayjs from 'dayjs' |
||||
import { isResError } from '@/hooks/useMessage' |
||||
|
||||
const router = useRouter() |
||||
|
||||
const shortcutList = [ |
||||
{ |
||||
title: '设备管理', |
||||
icon: 'solar:server-square-linear', |
||||
type: 'primary', |
||||
path: '/device/data', |
||||
}, |
||||
{ |
||||
title: '设备库存', |
||||
icon: 'solar:box-outline', |
||||
type: 'success', |
||||
path: '/storage', |
||||
}, |
||||
{ |
||||
title: '测试计划', |
||||
icon: 'codicon:code-oss', |
||||
type: 'warning', |
||||
path: '/testSheet/plan', |
||||
}, |
||||
] |
||||
|
||||
function onShortcutClick(item: any) { |
||||
router.push(item.path) |
||||
} |
||||
|
||||
const filterData = [ |
||||
{ |
||||
key: 0, |
||||
name: '本月', |
||||
}, |
||||
{ |
||||
key: 1, |
||||
name: '本年', |
||||
}, |
||||
] |
||||
|
||||
const summaryList = ref([ |
||||
// { |
||||
// label: '设备总数', |
||||
// value: 0, |
||||
// icon: 'solar:server-square-linear', |
||||
// color: '#1E90FF', |
||||
// }, |
||||
{ |
||||
label: '积累入库', |
||||
value: 0, |
||||
icon: 'solar:inbox-in-broken', |
||||
key: 'storage', |
||||
color: '#4CAF50', |
||||
}, |
||||
{ |
||||
label: '积累出库', |
||||
value: 0, |
||||
key: 'outStorage', |
||||
icon: 'solar:inbox-out-outline', |
||||
color: '#2196F3', |
||||
}, |
||||
{ |
||||
label: '测试中设备', |
||||
value: 0, |
||||
icon: 'solar:document-add-outline', |
||||
key: 'testing', |
||||
color: '#FF9800', |
||||
}, |
||||
{ |
||||
label: '维修中', |
||||
icon: 'solar:minimalistic-magnifer-bug-outline', |
||||
value: 0, |
||||
key: 'repair', |
||||
color: '#F44336', |
||||
}, |
||||
]) |
||||
|
||||
const currentFilter = ref(1) |
||||
function onTimeSearch(filter: number) { |
||||
currentFilter.value = filter |
||||
loadLineChartData() |
||||
} |
||||
const lineChartData = ref<any>([]) |
||||
async function loadLineChartData() { |
||||
// 获取当前月 |
||||
const curMonth = dayjs().month() + 1 |
||||
const curYear = dayjs().year() |
||||
const res = await getDeviceStorageAndMaintainChart({ |
||||
type: currentFilter.value, |
||||
year: curYear, |
||||
month: curMonth, |
||||
}) |
||||
if (isResError(res)) return |
||||
lineChartData.value = res.data |
||||
} |
||||
|
||||
const pieData = ref<any>([]) |
||||
async function loadPieChartData() { |
||||
const res = await getDeviceSummaryByStatus() |
||||
if (isResError(res)) return |
||||
pieData.value = res.data.map((item: any) => { |
||||
return { |
||||
name: item.statusName, |
||||
value: item.status, |
||||
} |
||||
}) |
||||
} |
||||
|
||||
async function loadDeviceSummary() { |
||||
const res = await getDeviceSummary() |
||||
if (isResError(res)) return |
||||
const data = res.data |
||||
summaryList.value.forEach(item => { |
||||
item.value = !!data[item.key] ? data[item.key] : 0 |
||||
}) |
||||
} |
||||
|
||||
onMounted(() => { |
||||
loadDeviceSummary() |
||||
loadLineChartData() |
||||
loadPieChartData() |
||||
}) |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
.device-home { |
||||
width: 100%; |
||||
height: 100%; |
||||
display: flex; |
||||
flex-direction: column; |
||||
row-gap: 16px; |
||||
.device-status-blok { |
||||
box-sizing: border-box; |
||||
height: 110px; |
||||
display: flex; |
||||
user-select: none; |
||||
justify-content: space-between; |
||||
column-gap: 40px; |
||||
.val-blok { |
||||
display: flex; |
||||
font-size: 16px; |
||||
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); |
||||
border-radius: 4px; |
||||
overflow: hidden; |
||||
background-color: var(--warp-bg); |
||||
height: 100%; |
||||
width: 33%; |
||||
padding: 0 30px; |
||||
align-items: center; |
||||
img { |
||||
width: 130px; |
||||
height: 100%; |
||||
} |
||||
|
||||
.val { |
||||
display: flex; |
||||
justify-content: center; |
||||
flex-direction: column; |
||||
width: 100%; |
||||
|
||||
row-gap: 6px; |
||||
|
||||
height: 100%; |
||||
.num { |
||||
font-size: 30px; |
||||
font-weight: 600; |
||||
padding-right: 24px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
main { |
||||
flex: 1; |
||||
display: flex; |
||||
column-gap: 16px; |
||||
.line { |
||||
flex: 0.58; |
||||
} |
||||
.pie { |
||||
flex: 0.42; |
||||
} |
||||
} |
||||
footer { |
||||
height: 140px; |
||||
.shortcut-list { |
||||
display: flex; |
||||
align-items: center; |
||||
column-gap: 18px; |
||||
} |
||||
.item { |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
cursor: pointer; |
||||
span { |
||||
margin-top: 8px; |
||||
color: var(--label-color); |
||||
} |
||||
} |
||||
:deep(.wrap-body) { |
||||
padding-top: 0; |
||||
padding-left: 20px; |
||||
} |
||||
:deep(.el-button) { |
||||
width: 52px; |
||||
height: 52px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
Loading…
Reference in new issue