提交 bb76d85f authored 作者: coderBryanFu's avatar coderBryanFu

feat:新增概览页时间线日历热力图功能

上级 bb99a95a
<script setup lang="ts">
import "@/styles/container.scss"
import TextStyle from './textStyle.vue';
import ConstStyle from './constStyle.vue';
import { ElScrollbar, ElSpace } from "element-plus";
</script>
<template> <template>
<el-scrollbar> <el-scrollbar>
<div class="common-page"> <div class="common-page">
<el-space direction="vertical" alignment="flex-start"> <el-space direction="vertical" alignment="flex-start">
<div class="text-title-0-show">开发样式</div> <div class="text-title-0-show">开发样式</div>
<div class="text-title-1-show">样式变量</div> <div class="text-title-1-show">样式变量</div>
<const-style></const-style> <ConstStyle />
<div class="text-title-1-show">文字样式</div> <div class="text-title-1-show">文字样式</div>
<text-style></text-style> <TextStyle />
</el-space> </el-space>
</div> </div>
</el-scrollbar> </el-scrollbar>
</template> </template>
\ No newline at end of file
<script setup>
import "@/styles/container.scss"
import TextStyle from './textStyle.vue';
import ConstStyle from './constStyle.vue';
</script>
\ No newline at end of file
<template> <template>
<table style="width: 100%; border-collapse: collapse; border: 1px solid #ebeef5;"> <table style="width: 1600px; border-collapse: collapse; border: 1px solid #ebeef5;">
<!-- 表头 --> <!-- 表头 -->
<thead> <thead>
<tr class="text-title-2"> <tr class="text-title-2">
......
...@@ -8,6 +8,7 @@ import { withFallbackImage } from "./utils"; ...@@ -8,6 +8,7 @@ import { withFallbackImage } from "./utils";
import "./styles/scrollbar.css"; import "./styles/scrollbar.css";
import "./styles/elui.css"; import "./styles/elui.css";
import "./styles/main.css"; import "./styles/main.css";
import "./styles/common.scss"
import '@/assets/fonts/font.css' import '@/assets/fonts/font.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
......
<template> <template>
<div style="position: relative;"> <div style="position: relative;">
<div ref="chartRef" style="height: 620px; width: 1640px" @mousemove="handleMouseMove"></div> <div ref="chartRef" style="height: 620px; width: 1640px" @click="handleClick"></div>
<div v-show="tooltipVisible" class="custom-tooltip background-as-card" <div
:style="{ left: mouseX + 'px', top: mouseY + 'px' }"> v-show="tooltipVisible"
<div class="tooltip-header flex-display"> class="custom-tooltip background-as-card"
<div class="tooltip-header-left text-title-3-bold">{{ currentDate }}</div> :style="{ left: mouseX + 'px', top: mouseY + 'px', cursor: dragging ? 'move' : 'default' }"
<div class="tooltip-header-right text-title-3-show">{{ `${'3个部门'}/${'3项举措'}` }}</div> @mousedown="startDrag"
>
<div
class="tooltip-header flex-display"
@mousedown.stop="startDrag"
style="cursor: move;"
>
<div class="tooltip-header-left text-title-3-bold">{{ currentDate }}</div>
<div class="tooltip-header-right text-title-3-show">{{ `${'3个部门'}/${'3项举措'}` }}</div>
</div> </div>
<div class="tooltip-main"> <div class="tooltip-main">
<div class="tooltip-main-item" v-for="item, index in currentDetailList" :key="index"> <div class="tooltip-main-item" v-for="item, index in currentDetailList" :key="index">
<div class="item-header flex-display"> <div class="item-header flex-display">
<div class="item-header-left flex-display"> <div class="item-header-left flex-display">
<div class="logo"> <div class="logo">
<img style="width:100%; height: 100%" :src="item.orgLogoUrl" alt="logo"> <img style="width:100%; height: 100%" :src="item.orgLogoUrl" alt="logo">
</div>
<div class="name text-bold">{{ item.orgName }}</div>
<div class="status">
<div class="status-on text-tip-2" v-if="item.stauts === 2">{{ '已落实' }}</div>
<div class="status-off text-tip-2" v-else>{{ '未落实' }}</div>
</div>
</div>
<div class="item-header-right flex-display">
<AreaTag v-for="tag, idx in item.techDomainList.slice(0,3)" :key="idx" :tagName="tag"></AreaTag>
</div>
</div> </div>
<div class="item-content text-compact">{{ item.name }}</div> <div class="name text-bold">{{ item.orgName }}</div>
<div class="item-footer"></div> <div class="status">
<div class="status-on text-tip-2" v-if="item.stauts === 2">{{ '已落实' }}</div>
<div class="status-off text-tip-2" v-else>{{ '未落实' }}</div>
</div>
</div>
<div class="item-header-right flex-display">
<AreaTag v-for="tag, idx in item.techDomainList.slice(0, 3)" :key="idx" :tagName="tag"></AreaTag>
</div> </div>
</div>
<div class="item-content text-compact">{{ item.name }}</div>
<div class="item-footer"></div>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
...@@ -53,6 +60,32 @@ const props = defineProps( ...@@ -53,6 +60,32 @@ const props = defineProps(
} }
); );
const dragging = ref(false);
let dragOffsetX = 0;
let dragOffsetY = 0;
const startDrag = (e) => {
dragging.value = true;
dragOffsetX = e.clientX - mouseX.value;
dragOffsetY = e.clientY - mouseY.value;
document.addEventListener('mousemove', onDrag);
document.addEventListener('mouseup', stopDrag);
}
const onDrag = (e) => {
if (dragging.value) {
mouseX.value = e.clientX - dragOffsetX;
mouseY.value = e.clientY - dragOffsetY;
}
}
const stopDrag = () => {
dragging.value = false;
document.removeEventListener('mousemove', onDrag);
document.removeEventListener('mouseup', stopDrag);
}
echarts.use([ echarts.use([
CustomChart, CustomChart,
...@@ -72,10 +105,18 @@ const mouseY = ref(0) ...@@ -72,10 +105,18 @@ const mouseY = ref(0)
const currentDate = ref('') const currentDate = ref('')
const currentDetailList = ref([]) const currentDetailList = ref([])
// 鼠标移动,记录位置 // // 鼠标移动,记录位置
const handleMouseMove = (e) => { const handleMouseMove = (e) => {
mouseX.value = e.clientX + 20 mouseX.value = e.clientX + 20
mouseY.value = e.clientY + 20 mouseY.value = e.clientY - 20
}
const handleClick = (e) => {
// mouseX.value = e.clientX - 160
// mouseY.value = e.clientY - 120
mouseX.value = e.clientX + 10
mouseY.value = e.clientY - 100
} }
// 计算展示的月份 获取今天在内的之后的三个月的月份 // 计算展示的月份 获取今天在内的之后的三个月的月份
...@@ -141,12 +182,27 @@ console.log('timelineData', props.timelineData); ...@@ -141,12 +182,27 @@ console.log('timelineData', props.timelineData);
const { dataMap, monthStats } = buildDataMap(props.timelineData); const { dataMap, monthStats } = buildDataMap(props.timelineData);
function getOption() { function getOption() {
const calendarWidthPercent = 30; const calendarWidthPercent = 30;
const gapPercent = (95 - (calendarWidthPercent * 3)) / 4; const gapPercent = (95 - (calendarWidthPercent * 3)) / 4;
// 计算所有天的最大 count
let maxCount = 0;
dataMap.forEach(item => {
if (item.count > maxCount) maxCount = item.count;
});
// 颜色插值函数
function getColorByCount(count) {
// rgb(231,243,255) -> rgb(137,193,255)
const start = [231, 243, 255];
const end = [137, 193, 255];
if (maxCount === 0) return `rgb(${start.join(',')})`;
const ratio = Math.min(count / maxCount, 1);
const rgb = start.map((s, i) => Math.round(s + (end[i] - s) * ratio));
return `rgb(${rgb.join(',')})`;
}
const calendars = months.map((month, index) => ({ const calendars = months.map((month, index) => ({
top: 40, top: 40,
left: `${gapPercent + index * (calendarWidthPercent + gapPercent)}%`, left: `${gapPercent + index * (calendarWidthPercent + gapPercent)}%`,
...@@ -167,14 +223,16 @@ function getOption() { ...@@ -167,14 +223,16 @@ function getOption() {
textStyle: { textStyle: {
rich: { rich: {
month: { month: {
fontSize: 24, fontSize: 18,
fontWeight: 'bold', fontWeight: 'bold',
color: '#1e3a8a', color: 'rgb(5, 95, 194)',
fontFamily: 'Source Han Sans CN'
}, },
stats: { stats: {
fontSize: 14, fontSize: 18,
color: '#1e40af', color: 'rgb(5, 95, 194)',
fontWeight: 'bold', fontWeight: 'bold',
fontFamily: 'Source Han Sans CN'
} }
} }
} }
...@@ -189,9 +247,6 @@ function getOption() { ...@@ -189,9 +247,6 @@ function getOption() {
monthData.push([dateStr, item]); monthData.push([dateStr, item]);
} }
// console.log('monthData', monthData);
return { return {
type: 'custom', type: 'custom',
coordinateSystem: 'calendar', coordinateSystem: 'calendar',
...@@ -210,14 +265,17 @@ function getOption() { ...@@ -210,14 +265,17 @@ function getOption() {
const y = cellPoint[1] - cellHeight / 2; const y = cellPoint[1] - cellHeight / 2;
const groupChildren = []; const groupChildren = [];
const bgColor = item.status === 'has_events' ? '#bfdbfe' : '#f0f9ff'; // 动态颜色
const bgColor = item.status === 'has_events'
? getColorByCount(item.count)
: '#f0f9ff';
groupChildren.push({ groupChildren.push({
type: 'rect', type: 'rect',
shape: { x, y, width: cellWidth, height: cellHeight }, shape: { x, y, width: cellWidth, height: cellHeight },
style: { style: {
fill: bgColor, fill: bgColor,
stroke: '#efefef', stroke: '#fff',
lineWidth: 1 lineWidth: 1
}, },
}); });
...@@ -252,7 +310,7 @@ function getOption() { ...@@ -252,7 +310,7 @@ function getOption() {
textAlign: 'left', textAlign: 'left',
textVerticalAlign: 'bottom', textVerticalAlign: 'bottom',
fontSize: 18, fontSize: 18,
fontWeight: 'bold', fontFamily: 'Source Han Sans CN'
}, },
}); });
} else { } else {
...@@ -266,7 +324,8 @@ function getOption() { ...@@ -266,7 +324,8 @@ function getOption() {
textAlign: 'center', textAlign: 'center',
textVerticalAlign: 'bottom', textVerticalAlign: 'bottom',
fontSize: 10, fontSize: 10,
fill: '#93c5fd', fill: 'rgb(185, 220, 255)',
fontFamily: 'Source Han Sans CN'
}, },
}); });
groupChildren.push({ groupChildren.push({
...@@ -278,8 +337,8 @@ function getOption() { ...@@ -278,8 +337,8 @@ function getOption() {
textAlign: 'center', textAlign: 'center',
textVerticalAlign: 'top', textVerticalAlign: 'top',
fontSize: 12, fontSize: 12,
fontWeight: 'bold', fill: 'rgb(185, 220, 255)',
fill: '#93c5fd', fontFamily: 'Source Han Sans CN'
}, },
}); });
} }
...@@ -292,7 +351,6 @@ function getOption() { ...@@ -292,7 +351,6 @@ function getOption() {
}; };
}); });
return { return {
tooltip: { show: false }, tooltip: { show: false },
title: titles, title: titles,
...@@ -307,7 +365,7 @@ onMounted(() => { ...@@ -307,7 +365,7 @@ onMounted(() => {
chartInstance.setOption(getOption()); chartInstance.setOption(getOption());
window.addEventListener('resize', resizeChart); window.addEventListener('resize', resizeChart);
chartInstance.on('mouseover', (params) => { chartInstance.on('click', (params) => {
console.log('params', params); console.log('params', params);
if (Array.isArray(params.data) && params.data.length >= 2) { if (Array.isArray(params.data) && params.data.length >= 2) {
...@@ -318,13 +376,15 @@ onMounted(() => { ...@@ -318,13 +376,15 @@ onMounted(() => {
currentDate.value = date currentDate.value = date
currentDetailList.value = list currentDetailList.value = list
tooltipVisible.value = true tooltipVisible.value = true
} else {
tooltipVisible.value = false
} }
} }
}); });
chartInstance.on('mouseout', () => { // chartInstance.on('mouseout', () => {
tooltipVisible.value = false // tooltipVisible.value = false
}); // });
} }
}); });
...@@ -347,11 +407,12 @@ onBeforeUnmount(() => { ...@@ -347,11 +407,12 @@ onBeforeUnmount(() => {
.custom-tooltip { .custom-tooltip {
position: fixed; position: fixed;
/* 使用 fixed 相对于视口定位 */ /* 使用 fixed 相对于视口定位 */
pointer-events: none; /* pointer-events: none; */
/* 让鼠标事件穿透到图表,避免闪烁 */ /* 让鼠标事件穿透到图表,避免闪烁 */
z-index: 1000; z-index: 1000;
width: 670px; width: 670px;
background: rgb(255, 255, 255); background: rgb(255, 255, 255);
} }
.tooltip-header { .tooltip-header {
...@@ -404,16 +465,19 @@ onBeforeUnmount(() => { ...@@ -404,16 +465,19 @@ onBeforeUnmount(() => {
background: rgba(206, 79, 81, 0.1); background: rgba(206, 79, 81, 0.1);
} }
.item-content{ .item-content {
height: 48px; height: 48px;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; /* 限制显示2行 */ -webkit-line-clamp: 2;
/* 限制显示2行 */
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
word-break: break-word; /* 可选:防止单词被截断 */ word-break: break-word;
/* 可选:防止单词被截断 */
} }
.item-header-right{
.item-header-right {
gap: 8px; gap: 8px;
} }
</style> </style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论