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 @@ |
|||||||
|
<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 @@ |
|||||||
|
<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 @@ |
|||||||
|
|
||||||
|
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 @@ |
|||||||
|
<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