feat: 优化上海街道地图展示并添加3D效果

- 将2D地图升级为3D地图展示,增强视觉效果
- 添加渐变炫彩边框样式提升UI美观度
- 修复类型检查错误并优化代码结构
- 更新vite配置允许特定主机访问
- 移除无用导入和注释代码
This commit is contained in:
yindongqi 2025-08-19 18:28:13 +08:00
parent 1241191f38
commit be85d94ad7
8 changed files with 3158 additions and 3125 deletions

4
.env
View File

@ -1,5 +1,5 @@
# VITE_MAINURL=http://127.0.0.1:8081/ VITE_MAINURL=http://127.0.0.1:8081/
VITE_MAINURL=http://localhost:8081/ # VITE_MAINURL=https://6k4n616846.goho.co/
# VITE_MAINURL=http://192.168.1.12:8081/ # VITE_MAINURL=http://192.168.1.12:8081/
# VITE_MAINURL=http://6adad28a.r36.cpolar.top # VITE_MAINURL=http://6adad28a.r36.cpolar.top
# VITE_MAINURL=https://10.86.138.22/shp/ # VITE_MAINURL=https://10.86.138.22/shp/

BIN
public/image/bg10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 KiB

View File

@ -1,4 +1,3 @@
import { component } from '@nutui/nutui/dist/types/__VUE/grid/common'
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [ const routes = [

View File

@ -2763,8 +2763,8 @@ const initStreetChart = () => {
map: "JiedaoMap", // 使 map: "JiedaoMap", // 使
geoIndex: 10, // geoIndex: 10, //
zoom: 0.87, // zoom: 0.87, //
left: streetLeft[districtValue.value], // 10% left: (streetLeft as any)[districtValue.value], // 10%
top: streetTop[districtValue.value], // top: (streetTop as any)[districtValue.value], //
// center: ["50%", "50%"], // // center: ["50%", "50%"], //
showLegendSymbol: false, // showLegendSymbol: false, //
label: { label: {

View File

@ -318,6 +318,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// @ts-nocheck
import VueDraggableResizable from "vue-draggable-resizable/src/components/vue-draggable-resizable.vue"; import VueDraggableResizable from "vue-draggable-resizable/src/components/vue-draggable-resizable.vue";
import "vue-draggable-resizable/dist/VueDraggableResizable.css"; import "vue-draggable-resizable/dist/VueDraggableResizable.css";
import { onMounted, reactive, ref, watch, onBeforeUnmount } from "vue"; import { onMounted, reactive, ref, watch, onBeforeUnmount } from "vue";

View File

@ -9,7 +9,8 @@
<!-- <div class="w-full h-screen bg-gradient-to-tr from-green-300 via-cyan-500 to-blue-400" <!-- <div class="w-full h-screen bg-gradient-to-tr from-green-300 via-cyan-500 to-blue-400"
:style="{ backgroundColor: basicOptions.backgroundColor }" id="mainContainer"> --> :style="{ backgroundColor: basicOptions.backgroundColor }" id="mainContainer"> -->
<div class="w-full h-screen bg-gradient-to-b from-blue-800 to-gray-800 relative overflow-hidden" id="mainContainer"> <div class="w-full h-screen relative overflow-hidden bg-[url(/image/bg10.png)] m-0 bg-cover bg-no-repeat bg-center"
id="mainContainer">
<!-- 科技感背景元素 --> <!-- 科技感背景元素 -->
<div class="absolute inset-0 w-full h-full overflow-hidden"> <div class="absolute inset-0 w-full h-full overflow-hidden">
<div <div
@ -25,9 +26,9 @@
class="absolute top-0 right-0 h-full w-[2px] bg-gradient-to-b from-transparent via-cyan-500 to-transparent opacity-70"> class="absolute top-0 right-0 h-full w-[2px] bg-gradient-to-b from-transparent via-cyan-500 to-transparent opacity-70">
</div> </div>
<!-- 科技感图案 --> <!-- 科技感图案 -->
<div class="absolute top-10 left-10 w-[200px] h-[200px] border-l-2 border-t-2 border-cyan-500 opacity-20"></div> <!-- <div class="absolute top-10 left-10 w-[200px] h-[200px] border-l-2 border-t-2 border-cyan-500 opacity-20"></div> -->
<div class="absolute bottom-10 right-10 w-[200px] h-[200px] border-r-2 border-b-2 border-cyan-500 opacity-20"> <!-- <div class="absolute bottom-10 right-10 w-[200px] h-[200px] border-r-2 border-b-2 border-cyan-500 opacity-20">
</div> </div> -->
<div <div
class="absolute top-0 left-0 w-full h-full bg-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDQwIDQwIj48ZyBmaWxsPSJub25lIiBzdHJva2U9IiMwYmI3ZmYiIHN0cm9rZS13aWR0aD0iMC4yIiBvcGFjaXR5PSIwLjIiPjxwYXRoIGQ9Ik0yMCAyMCBMMCAyMCBMMCA0MCIvPjxwYXRoIGQ9Ik0yMCAyMCBMNDAgMjAgTDQwIDQwIi8+PHBhdGggZD0iTTIwIDIwIEwwIDIwIEwwIDAiLz48cGF0aCBkPSJNMjAgMjAgTDQwIDIwIEw0MCAwIi8+PC9nPjwvc3ZnPg==')] bg-repeat opacity-5"> class="absolute top-0 left-0 w-full h-full bg-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDQwIDQwIj48ZyBmaWxsPSJub25lIiBzdHJva2U9IiMwYmI3ZmYiIHN0cm9rZS13aWR0aD0iMC4yIiBvcGFjaXR5PSIwLjIiPjxwYXRoIGQ9Ik0yMCAyMCBMMCAyMCBMMCA0MCIvPjxwYXRoIGQ9Ik0yMCAyMCBMNDAgMjAgTDQwIDQwIi8+PHBhdGggZD0iTTIwIDIwIEwwIDIwIEwwIDAiLz48cGF0aCBkPSJNMjAgMjAgTDQwIDIwIEw0MCAwIi8+PC9nPjwvc3ZnPg==')] bg-repeat opacity-5">
</div> </div>
@ -87,26 +88,30 @@
<!-- 左1 --> <!-- 左1 -->
<vue-draggable-resizable ref="refWoTe1" style="border: none; z-index: 999" :draggable="draggableBoolean" <vue-draggable-resizable ref="refWoTe1" style="border: none; z-index: 999" :draggable="draggableBoolean"
:resizable="resizableBoolean"> :resizable="resizableBoolean">
<div class="flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500" <div
class="fancy-border flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500"
ref="main1"></div> ref="main1"></div>
</vue-draggable-resizable> </vue-draggable-resizable>
<!-- 左2 --> <!-- 左2 -->
<vue-draggable-resizable ref="refWoTe2" style="border: none; z-index: 999" :draggable="draggableBoolean" <vue-draggable-resizable ref="refWoTe2" style="border: none; z-index: 999" :draggable="draggableBoolean"
:resizable="resizableBoolean"> :resizable="resizableBoolean">
<div class="flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500" <div
class="fancy-border flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500"
ref="main2"></div> ref="main2"></div>
</vue-draggable-resizable> </vue-draggable-resizable>
<!-- 左3 --> <!-- 左3 -->
<vue-draggable-resizable ref="refWoTe3" style="border: none; z-index: 999" :draggable="draggableBoolean" <vue-draggable-resizable ref="refWoTe3" style="border: none; z-index: 999" :draggable="draggableBoolean"
:resizable="resizableBoolean"> :resizable="resizableBoolean">
<div class="flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500" <div
class="fancy-border flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500"
ref="main3"></div> ref="main3"></div>
</vue-draggable-resizable> </vue-draggable-resizable>
<!-- 右1 --> <!-- 右1 -->
<vue-draggable-resizable ref="refWoTe4" style="border: none; z-index: 999" :draggable="draggableBoolean" <vue-draggable-resizable ref="refWoTe4" style="border: none; z-index: 999" :draggable="draggableBoolean"
:resizable="resizableBoolean"> :resizable="resizableBoolean">
<div class="flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500" <div
class="fancy-border flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500"
ref="main4"> ref="main4">
<div class="flex justify-around text-white"> <div class="flex justify-around text-white">
<div class="p-2 flex flex-col font-semibold items-center"> <div class="p-2 flex flex-col font-semibold items-center">
@ -142,7 +147,8 @@
<!-- 右2 --> <!-- 右2 -->
<vue-draggable-resizable ref="refWoTe5" style="border: none; z-index: 999;" :draggable="draggableBoolean" <vue-draggable-resizable ref="refWoTe5" style="border: none; z-index: 999;" :draggable="draggableBoolean"
:resizable="resizableBoolean"> :resizable="resizableBoolean">
<div class="flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500" <div
class="fancy-border flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500"
ref="main5"> ref="main5">
<div class="scroll-wrapper" v-if="defaultShow"> <div class="scroll-wrapper" v-if="defaultShow">
<div class="scroll-header"> <div class="scroll-header">
@ -325,20 +331,20 @@
<vue-draggable-resizable ref="refWoTe6" style="border: none; z-index: 999" :draggable="draggableBoolean" <vue-draggable-resizable ref="refWoTe6" style="border: none; z-index: 999" :draggable="draggableBoolean"
:resizable="resizableBoolean" v-show="!relationShow && !isAnimating"> :resizable="resizableBoolean" v-show="!relationShow && !isAnimating">
<div <div
class="flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500 overflow-hidden" class="fancy-border flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500"
ref="main6"> ref="main6">
</div> </div>
</vue-draggable-resizable> </vue-draggable-resizable>
<!-- 右下角按钮 --> <!-- 右下角按钮 -->
<div ref="fancyBtn" <div ref="fancyBtn"
class=" fixed z-[9999] bottom-10 right-7 px-3 py-1 rounded-full bg-gradient-to-r from-blue-500 via-cyan-400 to-purple-500 shadow-lg text-white font-bold text-lg cursor-pointer transition-all duration-300 ease-in-out hover:scale-110 hover:shadow-2xl active:scale-95 active:brightness-90 ring-2 ring-white/30 ring-offset-2 ring-offset-black backdrop-blur-md overflow-hidden" class=" fixed z-[9999] bottom-20 right-7 px-3 py-1 rounded-full bg-gradient-to-r from-blue-500 via-cyan-400 to-purple-500 shadow-lg text-white font-bold text-lg cursor-pointer transition-all duration-300 ease-in-out hover:scale-110 hover:shadow-2xl active:scale-95 active:brightness-90 ring-2 ring-white/30 ring-offset-2 ring-offset-black backdrop-blur-md overflow-hidden"
@click="handleFancyClick" style="user-select: none;"> @click="handleFancyClick" style="user-select: none;">
关联关系图 关联关系图
<span ref="ripple" class="ripple"></span> <span ref="ripple" class="ripple"></span>
</div> </div>
<div <div
class="flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500 overflow-hidden z-[999] fixed bottom-10 right-7" class="fancy-border flex items-center justify-center w-96 h-48 border-2 border-dashed rounded border-gray-500 z-[999]"
ref="main9" :class="{ ref="main9" :class="{
'animate-popup': isAnimating && relationShow, 'animate-popup': isAnimating && relationShow,
'animate-shrink': isAnimating && !relationShow, 'animate-shrink': isAnimating && !relationShow,
@ -408,6 +414,7 @@ import { router } from "../router";
import headMenu from "../components/headMenu.vue"; import headMenu from "../components/headMenu.vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import "echarts-wordcloud"; import "echarts-wordcloud";
import "echarts-gl";
import * as d3 from "d3"; import * as d3 from "d3";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
@ -1859,7 +1866,7 @@ const echarts3 = () => {
const echarts6_old = async () => { const echarts6_old = async () => {
var myChart = echarts.init(main6.value); var myChart = echarts.init(main6.value);
var option: EChartsOption; var option: EChartsOption;
const timeValueMap = { const timeValueMap: Record<string, number> = {
上三个月: -3, 上三个月: -3,
上二个月: -2, 上二个月: -2,
上一个月: -1, 上一个月: -1,
@ -2306,7 +2313,10 @@ const echarts6_old = async () => {
// console.log('littlemonth.value', littlemonth.value); // console.log('littlemonth.value', littlemonth.value);
// console.log('mockDataSou11', mockDataSou,"222",mockDataSource); // console.log('mockDataSou11', mockDataSou,"222",mockDataSource);
console.log("mockDataSou", mockDataSou); console.log("mockDataSou", mockDataSou);
const mockData = mockDataSou.timeScores.map((item) => item.scores); if (!mockDataSou) {
return;
}
const mockData = mockDataSou.timeScores.map((item: any) => item.scores);
console.log("mockData", mockData); console.log("mockData", mockData);
option = { option = {
baseOption: { baseOption: {
@ -2324,7 +2334,7 @@ const echarts6_old = async () => {
"下三个月", "下三个月",
], ],
currentIndex: 3, currentIndex: 3,
label: { formatter: (s: string) => s }, label: { formatter: (value: string | number, index: number) => String(value) },
}, },
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
@ -2405,9 +2415,10 @@ const echarts6_old = async () => {
myChart.setOption(option); myChart.setOption(option);
// //
myChart.on("timelinechanged", (params) => { myChart.on("timelinechanged", (params: any) => {
const currentName = option.baseOption.timeline.data[params.currentIndex]; const dataArr = ((option as any).baseOption.timeline.data as string[]);
const clickValue = timeValueMap[currentName]; const currentName = dataArr[params.currentIndex] as string;
const clickValue = timeValueMap[currentName] ?? 0;
littlemonth.value = clickValue; littlemonth.value = clickValue;
}); });
}; };
@ -2741,7 +2752,7 @@ const echartsYibiao = () => {
const initTimelineChart = () => { const initTimelineChart = () => {
var myChart = echarts.init(timeline.value); var myChart = echarts.init(timeline.value);
var option: EChartsOption; var option: EChartsOption;
const timeValueMap = { const timeValueMap: Record<string, number> = {
上三个月: -3, 上三个月: -3,
上二个月: -2, 上二个月: -2,
上一个月: -1, 上一个月: -1,
@ -2768,8 +2779,8 @@ const initTimelineChart = () => {
], ],
currentIndex: 3, // currentIndex: 3, //
label: { label: {
formatter: function (s) { formatter: function (value: string | number, index: number) {
return s; return String(value);
}, },
}, },
}, },
@ -2789,8 +2800,8 @@ const initTimelineChart = () => {
type: "shadow", type: "shadow",
label: { label: {
show: true, show: true,
formatter: function (params) { formatter: function (params: any) {
return params.value.replace("\n", ""); return String(params.value).replace("\n", "");
}, },
}, },
}, },
@ -2807,9 +2818,10 @@ const initTimelineChart = () => {
myChart.setOption(option); myChart.setOption(option);
// //
myChart.on("timelinechanged", (params) => { myChart.on("timelinechanged", (params: any) => {
const currentName = option.baseOption.timeline.data[params.currentIndex]; const dataArr = ((option as any).baseOption.timeline.data as string[]);
const clickValue = timeValueMap[currentName]; const currentName = dataArr[params.currentIndex] as string;
const clickValue = timeValueMap[currentName] ?? 0;
littlemonth.value = clickValue; littlemonth.value = clickValue;
SelectBlur(); SelectBlur();
}); });
@ -3466,137 +3478,95 @@ const initStreetChart = () => {
console.log("streetScore", streetScore); console.log("streetScore", streetScore);
const streetOption = { const streetOption3D: any = {
//
grid: {
right: "100%",
top: "10%",
// bottom: "10%",
width: "20%",
left: "12%", //
},
//
visualMap: { visualMap: {
pieces: scoreIntervals.map((interval) => ({ pieces: scoreIntervals.map((interval) => ({
gte: parseFloat(interval.min.toFixed(1)), gte: parseFloat(interval.min.toFixed(1)),
lte: parseFloat(interval.max.toFixed(1)), lte: parseFloat(interval.max.toFixed(1)),
color: interval.color, color: interval.color,
})), })),
show: true, // show: true,
// left: "83%",
left: "83%", // 10% top: "5%",
top: "5%", // orient: "vertical",
orient: "vertical", //
textStyle: { textStyle: {
color: "#dbeafe", color: "#dbeafe",
fontSize: 12, // fontSize: 12,
}, },
}, },
series: [ series: [
{ {
type: "map", // type: "map3D", //
map: "JiedaoMap", // 使 map: "JiedaoMap", // 使
geoIndex: 10, // data: streetScore, //
zoom: districtValue.value === "上海市" ? 1.2 : 0.9, // shading: "lambert", // lambert
// left: '18%', // 10%
// top: '15%', //
left: "center", left: "center",
top: districtValue.value === "上海市" ? "12%" : "15%", top: districtValue.value === "上海市" ? "-20%" : "0%",
right: "auto", height: 'auto',
bottom: "auto",
// center: [121.52, 30.94],
showLegendSymbol: false, //
label: { label: {
normal: { show: true, //
show: true, // // formatter: (p: any) => `${p.name}\n${p?.data?.value ?? ""}`, //
formatter: function (params: { name: any; data: { value: any } }) { formatter: (p: any) => {
const name = params.name; switch (p.name) {
const value = params.data ? params.data.value : ""; case '普陀区':
return `${name}\n${value}`; return `普陀\n${p?.data?.value ?? ""}`
break;
case '静安区':
return `静安\n${p?.data?.value ?? ""}`
break;
case '虹口区':
return `虹口\n${p?.data?.value ?? ""}`
break;
case '杨浦区':
return `杨浦\n${p?.data?.value ?? ""}`
break;
case '长宁区':
return `长宁\n${p?.data?.value ?? ""}`
break;
case '黄浦区':
return `黄浦\n${p?.data?.value ?? ""}`
break;
case '徐汇区':
return `徐汇\n${p?.data?.value ?? ""}`
break;
default:
return `${p.name}\n${p?.data?.value ?? ""}`
break;
}
}, },
textStyle: { textStyle: { color: "#fff", fontSize: 12, fontWeight: '700' }, //
color: "#fff", //
fontSize: 11, //
}, },
itemStyle: {
borderWidth: 0.5,
borderColor: "#FFFFFF", //
opacity: 1, //
}, },
emphasis: { emphasis: {
show: true, label: { show: true, textStyle: { color: "#fff" } }, //
textStyle: { color: "#fff" },
}, //
}, },
roam: false, // regionHeight: 5, //
itemStyle: { viewControl: {
normal: { areaColor: "#031525", borderColor: "#FFFFFF" }, // distance: 135, //
emphasis: { areaColor: "#2B91B7" }, // alpha: 45, //
beta: -15, //
panMouseButton: "left", //
rotateMouseButton: "right", //
}, },
animation: false, // light: {
data: streetScore, // main: { intensity: 1.2 }, //
}, ambient: { intensity: 0.3 }, //
{
type: "bar",
data: streetScore.map((item) => 0),
barWidth: 10, // 10px
barMaxWidth: 0, // 0
barMinWidth: 0, // 0
//
itemStyle: {
show: false,
color: "#dbeafe", //
borderColor: "transparent", //
},
label: {
show: false,
position: "right",
color: "#dbeafe",
fontSize: 15,
distance: 10,
formatter: (params: { dataIndex: number }) =>
streetScore[params.dataIndex].value,
//
overflow: "truncate", //
// height: 50, //
width: 50, //
align: "left", //
}, },
}, },
], ],
xAxis: {
type: "value",
position: "top",
show: false,
// splitLine: { show: false },
// axisLabel: { show: false },
// axisLine: { show: false },
// axisTick: { show: false }
},
yAxis: {
show: false,
type: "category",
data: streetScore.map((item) => item.name),
axisLabel: {
color: "#dbeafe", //
formatter: function (value: string) {
if (value === "上海市奉贤区海湾旅游区") {
return "上海...旅游区";
} else if (value === "上海海港综合经济开发区") {
return "上海...开发区";
}
return value;
},
},
boundaryGap: true,
axisLine: { show: true },
axisTick: { show: true },
},
}; };
streetChart.setOption(streetOption); streetChart.setOption(streetOption3D);
// //
streetChart.on("click", function (params) { streetChart.on("click", function (params) {
if ( if (
params.componentType === "series" && params.componentType === "series" &&
params.seriesType === "map" && params.seriesType === "map3D" &&
selectedStreet.value !== params.name selectedStreet.value !== params.name
) { ) {
selectedStreet.value = params.name; // selectedStreet.value = params.name; //
@ -3606,7 +3576,7 @@ const initStreetChart = () => {
// //
} else if ( } else if (
params.componentType === "series" && params.componentType === "series" &&
params.seriesType === "map" && params.seriesType === "map3D" &&
selectedStreet.value == params.name selectedStreet.value == params.name
) { ) {
// //
@ -3637,8 +3607,8 @@ const initRelationshipChart = () => {
chartRelationship = echarts.init(chartDom); chartRelationship = echarts.init(chartDom);
getRelationshipNetwork().then((res) => { getRelationshipNetwork().then((res) => {
let nodes; let nodes: any[];
let links; let links: any[];
// //
if (handleRowValue.value == "") { if (handleRowValue.value == "") {
// 线线 // 线线
@ -3672,7 +3642,7 @@ const initRelationshipChart = () => {
nodes = res.data.nodes nodes = res.data.nodes
// 线 // 线
.filter((node: any) => .filter((node: any) =>
links.some(link => links.some((link: any) =>
link.target === node.name || link.source === node.name link.target === node.name || link.source === node.name
) )
) )
@ -3759,7 +3729,7 @@ const initRelationshipChart = () => {
}; };
// //
chartRelationship.setOption(option); chartRelationship!.setOption(option as any);
}); });
// //
@ -3997,8 +3967,8 @@ const zhifaPinfaData = ref([
// 线 // 线
const choujianEchartsPinfa = () => { const choujianEchartsPinfa = () => {
// //
if (main7.value && echarts.getInstanceByDom(main7.value)) { if (main7.value && echarts.getInstanceByDom(main7.value as HTMLElement)) {
echarts.getInstanceByDom(main7.value).dispose(); echarts.getInstanceByDom(main7.value as HTMLElement)?.dispose();
} }
var myChart = echarts.init(main7.value); var myChart = echarts.init(main7.value);
const months = ["1月", "2月", "3月", "4月", "5月"]; const months = ["1月", "2月", "3月", "4月", "5月"];
@ -4061,14 +4031,13 @@ const choujianEchartsPinfa = () => {
// }, // },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
formatter: function (params) { formatter: function (params: any[]) {
let ymonth = ""; let ymonth = "";
let result = ''; let result = '';
const dataMap = {}; const dataMap: Record<string, { value: number | null; prediction: number | null }> = {};
// 线线 // 线线
params.forEach(item => { params.forEach((item: any) => {
// console.log(item);
const isPrediction = item.seriesName.includes('预测'); const isPrediction = item.seriesName.includes('预测');
const baseName = isPrediction ? item.seriesName.replace('预测', '') : item.seriesName; const baseName = isPrediction ? item.seriesName.replace('预测', '') : item.seriesName;
@ -4077,22 +4046,20 @@ const choujianEchartsPinfa = () => {
} }
if (isPrediction) { if (isPrediction) {
dataMap[baseName].prediction = item.value; dataMap[baseName].prediction = item.value as number | null;
} else { } else {
dataMap[baseName].value = item.value; dataMap[baseName].value = item.value as number | null;
} }
ymonth = item.name; ymonth = item.name;
// console.log(ymonth);
}); });
// tooltip Object.keys(dataMap).forEach((name: string) => {
Object.keys(dataMap).forEach(name => {
const item = dataMap[name]; const item = dataMap[name];
if (item.prediction && ymonth == "5月") { if (item.prediction != null && ymonth == "5月") {
result += `${name}: (预测: ${item.prediction}%)`; result += `${name}: (预测: ${item.prediction}%)`;
} else { } else {
result += `${name}: ${item.value || '0'}%`; result += `${name}: ${item.value ?? 0}%`;
} }
result += '<br/>'; result += '<br/>';
}); });
@ -4105,10 +4072,10 @@ const choujianEchartsPinfa = () => {
top: 30, top: 30,
textStyle: { color: "#fff" }, textStyle: { color: "#fff" },
// 线legend // 线legend
selected: legendData.reduce((acc, cur) => { selected: legendData.reduce((acc: Record<string, boolean>, cur: string) => {
acc[cur] = true; acc[cur] = true;
return acc; return acc;
}, {}), }, {} as Record<string, boolean>),
}, },
grid: { left: 45, right: 30, top: 63, bottom: 45 }, grid: { left: 45, right: 30, top: 63, bottom: 45 },
xAxis: { xAxis: {
@ -4130,8 +4097,8 @@ const choujianEchartsPinfa = () => {
const zhifaEchartsPinfa = () => { const zhifaEchartsPinfa = () => {
// //
if (main8.value && echarts.getInstanceByDom(main8.value)) { if (main8.value && echarts.getInstanceByDom(main8.value as HTMLElement)) {
echarts.getInstanceByDom(main8.value).dispose(); echarts.getInstanceByDom(main8.value as HTMLElement)?.dispose();
} }
var myChart = echarts.init(main8.value); var myChart = echarts.init(main8.value);
const months = ["1月", "2月", "3月", "4月", "5月"]; const months = ["1月", "2月", "3月", "4月", "5月"];
@ -4195,14 +4162,13 @@ const zhifaEchartsPinfa = () => {
// }, // },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
formatter: function (params) { formatter: function (params: any[]) {
let ymonth = ""; let ymonth = "";
let result = ''; let result = '';
const dataMap = {}; const dataMap: Record<string, { value: number | null; prediction: number | null }> = {};
// 线线 // 线线
params.forEach(item => { params.forEach((item: any) => {
// console.log(item);
const isPrediction = item.seriesName.includes('预测'); const isPrediction = item.seriesName.includes('预测');
const baseName = isPrediction ? item.seriesName.replace('预测', '') : item.seriesName; const baseName = isPrediction ? item.seriesName.replace('预测', '') : item.seriesName;
@ -4211,22 +4177,20 @@ const zhifaEchartsPinfa = () => {
} }
if (isPrediction) { if (isPrediction) {
dataMap[baseName].prediction = item.value; dataMap[baseName].prediction = item.value as number | null;
} else { } else {
dataMap[baseName].value = item.value; dataMap[baseName].value = item.value as number | null;
} }
ymonth = item.name; ymonth = item.name;
// console.log(ymonth);
}); });
// tooltip Object.keys(dataMap).forEach((name: string) => {
Object.keys(dataMap).forEach(name => {
const item = dataMap[name]; const item = dataMap[name];
if (ymonth == "5月") { if (ymonth == "5月") {
result += `${name}: (预测: ${item.prediction}%)`; result += `${name}: (预测: ${item.prediction}%)`;
} else { } else {
result += `${name}: ${item.value || '0'}%`; result += `${name}: ${item.value ?? 0}%`;
} }
result += '<br/>'; result += '<br/>';
}); });
@ -4239,10 +4203,10 @@ const zhifaEchartsPinfa = () => {
top: 30, top: 30,
textStyle: { color: "#fff" }, textStyle: { color: "#fff" },
// 线legend // 线legend
selected: legendData.reduce((acc, cur) => { selected: legendData.reduce((acc: Record<string, boolean>, cur: string) => {
acc[cur] = true; acc[cur] = true;
return acc; return acc;
}, {}), }, {} as Record<string, boolean>),
}, },
grid: { left: 45, right: 30, top: 63, bottom: 45 }, grid: { left: 45, right: 30, top: 63, bottom: 45 },
xAxis: { xAxis: {
@ -4320,6 +4284,7 @@ const jubaoPinfaData = ref([
]); ]);
const highlightGraphNodeByName = (nodeName: string) => { const highlightGraphNodeByName = (nodeName: string) => {
if (!chartRelationship) return;
// //
chartRelationship.dispatchAction({ chartRelationship.dispatchAction({
type: "downplay", type: "downplay",
@ -4351,8 +4316,8 @@ const handleRowClick = (item: any, index: number) => {
const fancyBtn = ref(null) const fancyBtn = ref(null)
const ripple = ref(null) const ripple = ref(null)
const handleFancyClick = (e: MouseEvent) => { const handleFancyClick = (e: MouseEvent) => {
const btn = fancyBtn.value as HTMLElement const btn = fancyBtn.value as unknown as HTMLElement
const span = ripple.value as HTMLElement const span = ripple.value as unknown as HTMLElement
if (!btn || !span) return if (!btn || !span) return
const rect = btn.getBoundingClientRect() const rect = btn.getBoundingClientRect()
const size = Math.max(rect.width, rect.height) const size = Math.max(rect.width, rect.height)
@ -4589,4 +4554,70 @@ const handleFancyClick = (e: MouseEvent) => {
transform: scale(0) !important; transform: scale(0) !important;
transition: none !important; transition: none !important;
} }
.fancy-border {
position: relative;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(4px);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.35),
inset 0 0 20px rgba(34, 211, 238, 0.15);
}
.fancy-border::before {
content: "";
position: absolute;
inset: -1px;
border-radius: inherit;
padding: 1px;
background: linear-gradient(90deg,
rgba(34, 211, 238, 0.6),
rgba(59, 130, 246, 0.6),
rgba(168, 85, 247, 0.6));
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
.fancy-border:hover {
box-shadow:
0 10px 28px rgba(0, 0, 0, 0.45),
inset 0 0 26px rgba(34, 211, 238, 0.25);
transition: box-shadow 0.25s ease;
}
/*
fancy-border 相关 CSS 解释
1. .fancy-border
- position: relative; //
- border-radius: 12px; //
- border: 1px solid rgba(255, 255, 255, 0.15); //
- background: rgba(255, 255, 255, 0.04); //
- backdrop-filter: blur(4px); //
- box-shadow:
0 8px 24px rgba(0, 0, 0, 0.35), //
inset 0 0 20px rgba(34, 211, 238, 0.15); //
2. .fancy-border::before
- content: ""; //
- position: absolute; //
- inset: -1px; // 1px
- border-radius: inherit; //
- padding: 1px; // 1px
- background: linear-gradient(90deg, ...); // --
- -webkit-mask mask-composite: //
- pointer-events: none; //
3. .fancy-border:hover
- box-shadow:
0 10px 28px rgba(0, 0, 0, 0.45), //
inset 0 0 26px rgba(34, 211, 238, 0.25); //
- transition: box-shadow 0.25s ease; //
整体效果实现了一个带有渐变炫彩边框玻璃拟态背景悬浮高亮的美观卡片样式
*/
</style> </style>

View File

@ -6,6 +6,8 @@ export default defineConfig({
plugins: [vue()], plugins: [vue()],
server: { server: {
//host: '0.0.0.0' //host: '0.0.0.0'
allowedHosts: ['294a8d70.r23.cpolar.top']
}, },
}) })

5914
yarn.lock

File diff suppressed because it is too large Load Diff