项目初始化
This commit is contained in:
@@ -17,4 +17,5 @@ export interface ChartInfo extends BasicModel<ChartInfo> {
|
||||
remark?: string; // 运行时长
|
||||
}
|
||||
|
||||
export const HostInfoData = () => defHttp.get<ChartInfo[]>({ url: adminPath + '/sys/analysis/getHostInfo' });
|
||||
export const HostInfoData = () =>
|
||||
defHttp.get<ChartInfo[]>({ url: adminPath + '/desktop/analysis/getHostInfo' });
|
||||
|
||||
@@ -17,20 +17,24 @@
|
||||
<div class="card-content">
|
||||
<div class="note-overview">
|
||||
<div class="note-metrics">
|
||||
<div v-for="item in metricCards" :key="item.key" class="metric-item">
|
||||
<div
|
||||
v-for="item in metricCards"
|
||||
:key="item.key"
|
||||
:class="['metric-item', { 'metric-item--active': selectedMetricKey === item.key }]"
|
||||
>
|
||||
<div class="metric-item__main">
|
||||
<div class="metric-item__pane metric-item__pane--left">
|
||||
<div class="metric-item__pane">
|
||||
<div class="metric-item__value" :style="{ color: item.color }">{{ item.total }}</div>
|
||||
<div class="metric-item__extra">总数</div>
|
||||
</div>
|
||||
<div class="metric-item__pane metric-item__pane--right">
|
||||
<div class="metric-item__pane">
|
||||
<div class="metric-item__value metric-item__value--small" :style="{ color: item.color }">{{
|
||||
item.finished
|
||||
}}</div>
|
||||
<div class="metric-item__extra">已完成</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-item__label">{{ item.label }}</div>
|
||||
<div class="metric-item__label" @click="handleMetricClick(item.key)">{{ item.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,15 +49,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import dayjs from 'dayjs';
|
||||
import { RefreshRight } from '@element-plus/icons-vue';
|
||||
import { DictData, dictDataListData } from '@jeesite/core/api/sys/dictData';
|
||||
import { MyNotes, myNotesListData } from '@jeesite/biz/api/biz/myNotes';
|
||||
import { NoteInfo, NoteInfoData, ChartDataItem, NoteChartData } from '@jeesite/biz/api/biz/myWorkbench';
|
||||
|
||||
interface MetricCard {
|
||||
key: string;
|
||||
label: string;
|
||||
value: number;
|
||||
color: string;
|
||||
total: number;
|
||||
finished: number;
|
||||
@@ -62,85 +63,51 @@
|
||||
const noteCardRef = ref<HTMLElement>();
|
||||
const chartRef = ref<HTMLElement>();
|
||||
const loading = ref(false);
|
||||
const noteList = ref<MyNotes[]>([]);
|
||||
const typeDict = ref<DictData[]>([]);
|
||||
const statusDict = ref<DictData[]>([]);
|
||||
const statusGroups = [
|
||||
{ key: 'pending', label: '待开始', color: '#F97316' },
|
||||
{ key: 'processing', label: '进行中', color: '#3B82F6' },
|
||||
{ key: 'finished', label: '已完成', color: '#10B981' },
|
||||
];
|
||||
const metricSource = ref<NoteInfo[]>([]);
|
||||
const chartData = ref<ChartDataItem[]>([]);
|
||||
const selectedMetricKey = ref('work');
|
||||
|
||||
const metricTypeGroups = [
|
||||
{ key: 'work', label: '工作', color: '#3B82F6' },
|
||||
{ key: 'life', label: '生活', color: '#10B981' },
|
||||
{ key: 'study', label: '学习', color: '#F97316' },
|
||||
{ key: 'other', label: '其他', color: '#8B5CF6' },
|
||||
];
|
||||
const metricCards = computed<MetricCard[]>(() => {
|
||||
const colors = ['#3B82F6', '#10B981', '#F97316', '#8B5CF6', '#EC4899', '#06B6D4'];
|
||||
return metricSource.value.map((item, index) => {
|
||||
return {
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
color: colors[index % colors.length],
|
||||
total: Number(item.value01 || 0),
|
||||
finished: Number(item.value02 || 0),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
let chartInstance: echarts.ECharts | null = null;
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
let themeObserver: MutationObserver | null = null;
|
||||
|
||||
const metricCards = computed<MetricCard[]>(() => {
|
||||
return metricTypeGroups.map((group) => ({
|
||||
key: group.key,
|
||||
label: group.label,
|
||||
color: group.color,
|
||||
value: noteList.value.filter((item) => matchTypeGroup(item.type, group.label)).length,
|
||||
total: noteList.value.filter((item) => matchTypeGroup(item.type, group.label)).length,
|
||||
finished: noteList.value.filter((item) => matchTypeGroup(item.type, group.label) && item.ustatus === '1').length,
|
||||
}));
|
||||
});
|
||||
|
||||
function getDictLabel(dictList: DictData[], value?: string) {
|
||||
return dictList.find((item) => item.dictValue === value)?.dictLabelRaw || value || '-';
|
||||
}
|
||||
|
||||
function matchTypeGroup(typeValue: string | undefined, targetLabel: string) {
|
||||
return getDictLabel(typeDict.value, typeValue) === targetLabel;
|
||||
}
|
||||
|
||||
function getStatusGroup(item: MyNotes) {
|
||||
const label = getDictLabel(statusDict.value, item.ustatus);
|
||||
if (label.includes('完成')) return '已完成';
|
||||
if (label.includes('进行')) return '进行中';
|
||||
if (label.includes('开始')) return '待开始';
|
||||
if (item.ustatus === '1') return '已完成';
|
||||
if (item.ustatus === '2') return '进行中';
|
||||
return '待开始';
|
||||
}
|
||||
|
||||
function getMonthList() {
|
||||
return Array.from({ length: 12 }, (_, index) => `${index + 1}月`);
|
||||
}
|
||||
|
||||
function getNoteMonth(item: MyNotes) {
|
||||
const dateValue = item.createTime || item.startTime || item.deadline || item.updateTime;
|
||||
return dateValue ? dayjs(dateValue).format('M月') : '';
|
||||
}
|
||||
|
||||
async function getDict() {
|
||||
try {
|
||||
const [typeRes, statusRes] = await Promise.all([
|
||||
dictDataListData({ dictType: 'note_type' }),
|
||||
dictDataListData({ dictType: 'note_status' }),
|
||||
]);
|
||||
typeDict.value = typeRes || [];
|
||||
statusDict.value = statusRes || [];
|
||||
} catch (error) {
|
||||
typeDict.value = [];
|
||||
statusDict.value = [];
|
||||
}
|
||||
async function handleMetricClick(key: string) {
|
||||
selectedMetricKey.value = key;
|
||||
await getChartData();
|
||||
nextTick(() => {
|
||||
renderChart();
|
||||
});
|
||||
}
|
||||
|
||||
async function getList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await myNotesListData({ pageNum: 1, pageSize: 999 });
|
||||
noteList.value = res?.list || [];
|
||||
const [metricRes, metricChartRes] = await Promise.all([
|
||||
NoteInfoData(),
|
||||
NoteChartData({ type: selectedMetricKey.value }),
|
||||
]);
|
||||
metricSource.value = metricRes || [];
|
||||
chartData.value = metricChartRes || [];
|
||||
} catch (error) {
|
||||
noteList.value = [];
|
||||
metricSource.value = [];
|
||||
chartData.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
nextTick(() => {
|
||||
@@ -149,43 +116,56 @@
|
||||
}
|
||||
}
|
||||
|
||||
function buildChartData() {
|
||||
const months = getMonthList();
|
||||
const totals = months.map((month) => {
|
||||
return statusGroups.reduce((sum, status) => {
|
||||
return (
|
||||
sum +
|
||||
noteList.value.filter((item) => getNoteMonth(item) === month && getStatusGroup(item) === status.label).length
|
||||
);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
const series: echarts.BarSeriesOption[] = statusGroups.map((group) => {
|
||||
return {
|
||||
name: group.label,
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
barWidth: '24%',
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
formatter: ({ value }) => (Number(value) > 0 ? `${value}` : ''),
|
||||
color: '#ffffff',
|
||||
fontSize: 11,
|
||||
},
|
||||
itemStyle: {
|
||||
color: group.color,
|
||||
borderRadius: 0,
|
||||
},
|
||||
data: months.map((month) => {
|
||||
return noteList.value.filter((item) => getNoteMonth(item) === month && getStatusGroup(item) === group.label)
|
||||
.length;
|
||||
}),
|
||||
async function getChartData() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const reqParams = {
|
||||
type: selectedMetricKey.value,
|
||||
};
|
||||
});
|
||||
const metricRes = await NoteChartData(reqParams);
|
||||
chartData.value = metricRes || [];
|
||||
} catch (error) {
|
||||
chartData.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function buildChartData() {
|
||||
const statusGroups = [
|
||||
{ label: '待开始', color: '#F97316', field: 'value01' },
|
||||
{ label: '进行中', color: '#3B82F6', field: 'value02' },
|
||||
{ label: '已完成', color: '#10B981', field: 'value03' },
|
||||
] as const;
|
||||
const months = chartData.value.map((item) => item.axisName || '-');
|
||||
const pendingData = chartData.value.map((item) => Number(item.value01 || 0));
|
||||
const processingData = chartData.value.map((item) => Number(item.value02 || 0));
|
||||
const finishedData = chartData.value.map((item) => Number(item.value03 || 0));
|
||||
const totals = chartData.value.map(
|
||||
(item) => Number(item.value01 || 0) + Number(item.value02 || 0) + Number(item.value03 || 0),
|
||||
);
|
||||
|
||||
const series: echarts.BarSeriesOption[] = statusGroups.map((group, groupIndex) => ({
|
||||
name: group.label,
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
barWidth: '24%',
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
formatter: ({ value }) => (Number(value) > 0 ? `${value}` : ''),
|
||||
color: '#ffffff',
|
||||
fontSize: 11,
|
||||
},
|
||||
itemStyle: {
|
||||
color: group.color,
|
||||
borderRadius: 0,
|
||||
},
|
||||
data: [pendingData, processingData, finishedData][groupIndex] || [],
|
||||
}));
|
||||
|
||||
series.push({
|
||||
name: '总数',
|
||||
@@ -225,9 +205,8 @@
|
||||
chartInstance = echarts.init(chartRef.value);
|
||||
}
|
||||
|
||||
const { categories, series } = buildChartData();
|
||||
const { categories, series, title } = buildChartData();
|
||||
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
||||
|
||||
const totalSeries = series.find((item) => item.name === '总数');
|
||||
if (totalSeries?.label) {
|
||||
totalSeries.label.color = isDark ? '#cbd5e1' : '#475569';
|
||||
@@ -237,10 +216,20 @@
|
||||
grid: {
|
||||
left: 12,
|
||||
right: 12,
|
||||
top: 40,
|
||||
bottom: 6,
|
||||
top: 52,
|
||||
bottom: 10,
|
||||
containLabel: true,
|
||||
},
|
||||
title: {
|
||||
text: title,
|
||||
left: 12,
|
||||
top: 6,
|
||||
textStyle: {
|
||||
color: isDark ? '#e2e8f0' : '#334155',
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: isDark ? 'rgba(20, 20, 20, 0.96)' : 'rgba(255, 255, 255, 0.96)',
|
||||
@@ -254,11 +243,11 @@
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
top: 4,
|
||||
top: 6,
|
||||
left: 'center',
|
||||
selectedMode: true,
|
||||
itemGap: 16,
|
||||
data: statusGroups.map((item) => item.label),
|
||||
data: ['待开始', '进行中', '已完成'],
|
||||
textStyle: {
|
||||
color: isDark ? '#e2e8f0' : '#475569',
|
||||
},
|
||||
@@ -306,9 +295,7 @@
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getDict();
|
||||
await getList();
|
||||
|
||||
if (noteCardRef.value) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
resizeChart();
|
||||
@@ -402,6 +389,19 @@
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
padding: 8px;
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease,
|
||||
border-color 0.2s ease;
|
||||
|
||||
&--active {
|
||||
box-shadow: 0 12px 28px rgb(59 130 246 / 18%);
|
||||
outline: 1px solid rgb(147 197 253);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&__main {
|
||||
display: flex;
|
||||
@@ -425,14 +425,6 @@
|
||||
box-shadow: 0 8px 24px rgb(148 163 184 / 14%);
|
||||
}
|
||||
|
||||
&__extra {
|
||||
margin-top: 8px;
|
||||
color: rgb(100 116 139);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
@@ -444,6 +436,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__extra {
|
||||
margin-top: 8px;
|
||||
color: rgb(100 116 139);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -457,6 +457,7 @@
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,18 +491,23 @@
|
||||
}
|
||||
|
||||
.metric-item {
|
||||
&--active {
|
||||
box-shadow: 0 14px 30px rgb(37 99 235 / 20%);
|
||||
outline-color: rgb(96 165 250);
|
||||
}
|
||||
|
||||
&__pane {
|
||||
background: linear-gradient(180deg, rgb(20, 20, 20) 0%, rgb(28 28 28) 100%);
|
||||
box-shadow: 0 10px 24px rgb(0 0 0 / 24%);
|
||||
}
|
||||
|
||||
&__extra {
|
||||
&__extra,
|
||||
&__label {
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
|
||||
&__label {
|
||||
border-top-color: rgb(51 65 85);
|
||||
color: rgb(148 163 184);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,10 +521,6 @@
|
||||
.note-metrics {
|
||||
grid-template-rows: repeat(2, minmax(88px, 1fr));
|
||||
}
|
||||
|
||||
.note-chart {
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user