Vue3 + TS + Vite —— 大屏可视化 项目实战

news/2024/11/7 22:48:58/

9a69fede8b2044a79dd834e3e48f20b4.png前期回顾f8e3cc1a0f694ac2b665ca2ad14c49d7.png  

Vue3 + Ts + Vite + pnpm 项目中集成 —— eslint 、prettier、stylelint、husky、commitizen_彩色之外的博客-CSDN博客搭建VIte + Ts + Vue3项目并集成eslint 、prettier、stylelint、huskyhttps://blog.csdn.net/m0_57904695/article/details/129950163?spm=1001.2014.3001.5502

目录

👍 适合谁

🎨 资料在哪

🏆 技术栈有哪些 

 🚀 效果图例

⏰ 配置缩放 【重要】

🚢 自动轮播地图

⌚ 时间

🔱 定位、天气

🎉   谢谢观看 :


👍 适合谁

1 、大学即将毕业 或者 自学前端 缺乏项目经验的
2 、入职以后需要做 Vue 系统的、需要跨过Vue2的直接学习Vue3的
3 、后端开发 没有前端经验 要做 vue + ts + java 项目的 
4、 缺乏vue实战项目经验 基础不是很好的 本教程非常的详细 每一步都总结在md文档里面
 

🎨 资料在哪

文章最末,所有博文相关代码全部都在仓库中 ,注意所有数据均为假数据模拟,如有雷同纯属巧合,除学术研究以外不做任何适用范围,分享的是技术路径、排名无先后

🏆 技术栈有哪些 

今天会从零开始搭建一个 Vue3 + Ts + Vite + pnpm 的大屏可视化项目,项目中集成 —— eslint 、prettier、stylelint、husky、commitizen,采用组件化封装思想、hooks工具、更好的符合企业级项目。

 🚀 效果图例

 

⏰ 配置缩放 【重要】


<script setup lang="ts">
/**** 盒子的原点默认在正中间,我们需要让原点在屏幕的正中间,如果原点不在屏幕正中间放大缩小,有可能会超出屏幕。* 解决方法:* 我们使用固定定位让盒子负50%,再把盒子的原点设置为左上角,这样原点就在屏幕的正中间了,* 屏幕宽度除以设计稿的宽度得到你放大或缩小的比例倍数,具体用宽or高的比例,需要判断,* 当屏幕宽度大于高度的时候(说明高度变小),我们取高度的比例,反之取宽度的比例。* 用这个倍数在你页面加载的时候先缩放,再将盒子归位(之前负了5盒子0%)。getScale()方法就是用来计算这个比例的。*/
import { onMounted, ref } from "vue";const screenRef = ref();
// 获取缩放比例
function getScale(_w = 1920, _h = 1080) {const ww = window.innerWidth / _w;const wh = window.innerHeight / _h;// ww > wh 什么情况下是true?当屏幕宽度大于高度的时候,我们取高度的比例,反之取宽度的比例return ww > wh ? wh : ww;
}
// 封装缩放方法
function scale() {const scale = getScale();// 先缩放在归位screenRef.value.style.transform = `scale(${scale}) translate(-50%, -50%)`;
}
onMounted(() => {scale();
});window.addEventListener("resize", () => {// 监听屏幕变化,重新计算缩放比例,并且添加过渡效果screenRef.value.style.transition = "all 1s linear";scale();
});
</script>

🚢 自动轮播地图

<template><!-- 中国地图 省级 一级页面 --><div id="main"></div>
</template><script setup lang="ts">
import * as echarts from "echarts";
import jsonData from "@/assets/china.json";
import { onBeforeUnmount, onMounted } from "vue";
import { useRouter } from "vue-router";
let timer: NodeJS.Timeout;
const router = useRouter();interface DataItem {ename: string;name: string;value?: number; // 这里添加了一个可选属性 value,它表示该地区的值
}const dataList: DataItem[] = [{ ename: "beijing", name: "北京" },{ ename: "tianjin", name: "天津" },{ ename: "shanghai", name: "上海" },{ ename: "chongqing", name: "重庆" },{ ename: "hebei", name: "河北" },{ ename: "henan", name: "河南" },{ ename: "yunnan", name: "云南" },{ ename: "liaoning", name: "辽宁" },{ ename: "heilongjiang", name: "黑龙江" },{ ename: "hunan", name: "湖南" },{ ename: "anhui", name: "安徽" },{ ename: "shandong", name: "山东" },{ ename: "xinjiang", name: "新疆" },{ ename: "jiangsu", name: "江苏" },{ ename: "zhejiang", name: "浙江" },{ ename: "jiangxi", name: "江西" },{ ename: "hubei", name: "湖北" },{ ename: "guangxi", name: "广西" },{ ename: "gansu", name: "甘肃" },{ ename: "jin", name: "山西" },{ ename: "neimenggu", name: "内蒙古" },{ ename: "shanxi", name: "陕西" },{ ename: "jilin", name: "吉林" },{ ename: "fujian", name: "福建" },{ ename: "guizhou", name: "贵州" },{ ename: "guangdong", name: "广东" },{ ename: "qinghai", name: "青海" },{ ename: "xizang", name: "西藏" },{ ename: "sichuan", name: "四川" },{ ename: "ningxia", name: "宁夏" },{ ename: "hainan", name: "海南" },{ ename: "taiwan", name: "台湾" },{ ename: "xianggang", name: "香港" },{ ename: "aomen", name: "澳门" },{ ename: "nanhaizhudao", name: "南海诸岛" },
];/**
* @method autoHover
* @description  自动高亮并轮播显示 tooltip
* @ParamsDescription  seriesIndex 参数为 0,表示第一个系列;dataIndex 参数则使用 index - 1 来指定上一个数据项的索引,以便取消其高亮状态。如果不提供 dataIndex 参数,则将取消当前系列的所有数据项的高亮状态。
* @returns  void
* @example  autoHover(chat)
* @author zk
* @createDate 2023/06/10 17:34:15
* @lastFixDate 2023/06/10 17:34:15
*/
function autoHover(chat: {dispatchAction: (arg0: {type: string;seriesIndex: number;dataIndex?: number; // 注意这里改为可选属性,因为取消高亮时不需要指定 dataIndex。}) => void;
}) {let index = 0;function startTimer() {timer = setInterval(function () {chat.dispatchAction({type: "downplay",seriesIndex: 0,dataIndex: index - 1, // 取消上一个数据项的高亮。});chat.dispatchAction({type: "highlight",seriesIndex: 0,dataIndex: index, // 高亮当前数据项。});// 显示 tooltipchat.dispatchAction({type: "showTip",seriesIndex: 0,dataIndex: index,});index++;if (index >= dataList.length) {// 注意这里改为大于等于,否则最后一个数据项无法高亮。index = 0;}}, 2000);}// 停止定时器并取消所有高亮function stopTimer() {clearInterval(timer);chat.dispatchAction({type: "downplay",seriesIndex: 0,});}startTimer();const chartElement = document.getElementById("main")!;chartElement.addEventListener("mouseenter", stopTimer);chartElement.addEventListener("mouseleave", startTimer);
}// 页面卸载之前清除定时器
onBeforeUnmount(() => {clearInterval(timer);
});onMounted(() => {const myChart = echarts.init(document.getElementById("main") as HTMLElement);// 注册中国地图 第一个参数为地图的名字,第二个参数为地图的json数据,第一个要和geo map一样echarts.registerMap("china", jsonData as never);// 模拟数据,给dataList添加一个随机的value值for (let i = 0; i < dataList.length; i++) {dataList[i].value = Math.floor(Math.random() * 1000 - 1);}const option = {tooltip: {trigger: "item",// 背景颜色backgroundColor: "#1f2a64",// 边框颜色borderColor: "#FFFFCC",// 阴影shadowColor: "#ffc706",//文字颜色textStyle: {color: "#fff",},// 显示延迟,添加显示延迟可以避免频繁切换,showDelay: 0,// 隐藏延迟,hideDelay: 0,// 是否显示提示框浮层enterable: true,// 提示框浮层的移动距离过渡transitionDuration: 0,extraCssText: "z-index:100",// {a}(系列名称),{b}(数据项名称),{c}(数值), {d}(百分比)可以使用标签formatter: "{b} :<br/>贷款人数:{c}人",},visualMap: {min: 0,max: 1000,text: ["高", "低"], //两端的文本realtime: false,calculable: true,itemWidth: 20, //图形的宽度,即长条的宽度。itemHeight: 90, //图形的高度,即长条的高度。align: "auto", //指定组件中手柄和文字的摆放位置.可选值为:‘auto’ 自动决定。‘left’ 手柄和label在右。‘right’ 手柄和label在左。‘top’ 手柄和label在下。‘bottom’ 手柄和label在上。left: "left", //组件离容器左侧的距离,‘left’, ‘center’, ‘right’,‘20%’top: "60%", //组件离容器上侧的距离,‘top’, ‘middle’, ‘bottom’,‘20%’right: "auto", //组件离容器右侧的距离,‘20%’bottom: "auto", //组件离容器下侧的距离,‘20%’orient: "vertical", //图例排列方向inRange: {color: ["#141c48", "#0d3d86"],},//设置字体颜色textStyle: {color: "#ffffff",},// 禁止点击分段型视觉映射组件selectedMode: false,},geo: {map: "china",roam: true, //是否开启平游或缩放zoom: 1.2, //当前视角的缩放比例emphasis: {label: {color: "#000",fontSize: 14,},// 鼠标放上高亮样式itemStyle: {areaColor: "#389BB7",borderWidth: 0,},},label: {// 通常状态下的样式show: true,color: "#fff",fontSize: 14,},// 地图区域的样式设置itemStyle: {borderColor: "rgba(147, 235, 248, 1)",borderWidth: 1,areaColor: {type: "radial",x: 0.5,y: 0.5,r: 0.8,colorStops: [{offset: 0,color: "rgba(147, 235, 248, 0)", // 0% 处的颜色},{offset: 1,color: "rgba(147, 235, 248, .2)", // 100% 处的颜色},],globalCoord: false,},},},// 鼠标悬浮提示框series: [{name: "省份",type: "map",geoIndex: 0,data: dataList,},],};//设置配置项myChart.setOption(option);// 自动轮播autoHover(myChart);// 点击事件地图 enmae为获取省地图的json数据// router.push({//     path: "/province",//     query: { provinceName: "tianjin", province: "天津" },//   });myChart.on("click", function (params: any) {// console.log("😂👨🏾‍❤️‍👨🏼==>: ", params.data.ename, params.name);  //===>打印后类似 xinjiang 新疆router.push({path: "/province",query: { provinceName: params.data.ename, province: params.name },});});// 缩放适应window.addEventListener("resize", () => {myChart.resize();});
});
</script><style scoped lang="scss">
#main {position: relative;top: -29%;width: 100%;height: 800px;
}
</style>

使用封装:

新建autoEchartsTooltip.ts

interface Option {time: number;isLoop: boolean;
}class TooltipAuto {// 功能相关的配置项option: Option = {time: 3000,isLoop: true,};// 数据索引,要显示那条数据的tooltipdataIndex = 0;// 保存时间函数的指针,方便清除timeTicket: ReturnType<typeof setInterval> | undefined;// 实例化出来的echartchart = {};// echarts的相关配置chartOptions = {};dataLength = 0;seriesIndex = 0;constructor(chart: any, chartOptions: any, option: Option) {this.option = option;this.chart = chart;this.chartOptions = chartOptions;}init() {this.showTooltipLoop();this.addMouseEvent();}// 展示tooltipshowTooltip() {const series = this.chartOptions.series;// 这里简单只处理地图的情况,series中的第一个为地图的配置项this.dataLength = series[this.seriesIndex].data.length;// 取消之前高亮的地图this.downplay();// 高亮当前图形this.highlight();this.showTip();}// 显示tooltipshowTip() {this.chart.dispatchAction({type: "showTip",seriesIndex: this.seriesIndex,dataIndex: this.dataIndex,});}// 隐藏tooltiphideTip() {this.chart.dispatchAction({type: "hideTip",});}// 高亮图形highlight() {this.chart.dispatchAction({type: "highlight",seriesIndex: this.seriesIndex,dataIndex: this.dataIndex,});}// 取消高亮downplay() {this.chart.dispatchAction({type: "downplay",seriesIndex: this.seriesIndex,dataIndex:this.dataIndex === 0 ? this.dataLength - 1 : this.dataIndex - 1,});}// 循环展示tooltipshowTooltipLoop() {this.timeTicket && clearInterval(this.timeTicket);if (this.option.isLoop) {this.showTooltip();this.timeTicket = setInterval(() => {if (this.dataIndex < this.dataLength - 1) {this.dataIndex++;} else {this.dataIndex = 0;}this.showTooltip();}, this.option.time);}}// 关闭循环展示closeTooltipLoop() {if (this.timeTicket) {clearInterval(this.timeTicket);this.timeTicket = null;this.hideTip();this.chart.dispatchAction({type: "downplay",seriesIndex: this.seriesIndex,dataIndex: this.dataIndex,});}}// 为地图添加鼠标事件,当鼠标移动时,停止轮播addMouseEvent() {this.chart.on("mousemove", this.mouseEventCallback.bind(this));// 鼠标离开时继续轮播this.chart.on("globalout", () => {this.showTooltipLoop();});}mouseEventCallback(param: any) {if (param.event) {// 阻止canvas上的鼠标移动事件冒泡param.event.cancelBubble = true;}this.closeTooltipLoop();}
}export function initTooltip(chart: any, chartOptions: any, option: Option) {const tooltip = new TooltipAuto(chart, chartOptions, option);tooltip.init();
}

使用:

initTooltip(myChart, option, {time: 3000,isLoop: true,});

⌚ 时间

<template><div class="nowTime"><i class="iconfont icon-weibiaoti-"></i><p class="date">{{ currentDate }}</p><p class="time">&nbsp;{{ currentTime }}</p></div>
</template><script setup lang="ts">
import { ref, onMounted } from "vue";const currentTime = ref<string>("");
const currentDate = ref<string>("");const updateTime = () => {const date = new Date();currentTime.value = date.toLocaleTimeString();currentDate.value = `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日 ${["周日", "周一", "周二", "周三", "周四", "周五", "周六"][date.getDay()]}`;
};onMounted(() => {updateTime();setInterval(updateTime, 1000);
});
</script><style lang="scss" scoped>
@import url("@/assets/iconFont/iconfont.css");
.icon-weibiaoti- {font-size: 30px;color: #0585e8;margin-right: 10px;
}
.nowTime {position: absolute;display: flex;right: 100px;top: 15px;font-size: 16px;.time,.date {font-size: 26px;background-image: linear-gradient(to right, #d38328, #bd5717, #807568);-webkit-background-clip: text;-webkit-text-fill-color: transparent;animation: shine 3s ease-in-out infinite;}@keyframes shine {0% {background-position: 0 0;}100% {background-position: 100px 0;}}.date {font-size: 20px;background-image: linear-gradient(to right, #fe6601, #ff953c, #fc741a);}
}
</style>

🔱 定位、天气

可参考vue项目中嵌入「天气预报」功能 - 掘金

<template><!-- 天气 page --><div class="weather" v-show="showFlag"><span>{{ province }} - {{ city }} - {{ weather }}</span><i :class="iconUrl"></i><span style="font-size: 25px; margin-left: 10px">{{ temperature }} </span>&nbsp;<span style="font-size: 16px">℃</span></div>
</template><script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { getLocation, getWeather } from "@/api/api";
let showFlag = ref(false);
const province = ref("");
const city = ref("");
const weather = ref("");
const temperature = ref("");// 获取位置
async function getInfoFn() {// 如果用户短时间多次进入页面,则不再请求if (showFlag.value) return;const { data: res } = await getLocation();if (res.status === 0) return console.log("获取位置失败");const abCode = res.adcode;// 存入时间和code 用于判断是否需要重新请求localStorage.setItem("locationInfo",JSON.stringify({ time: Date.now(), code: abCode }));getWeatherFn(abCode);
}// 获取天气
async function getWeatherFn(payLoad: string) {const { data: res } = await getWeather({city: payLoad,key: "33f7405fa0049ff120947b37a12567b2",});if (res.status === 0) return console.log("获取天气失败");showFlag.value = true;province.value = res.lives[0].province;city.value = res.lives[0].city;weather.value = res.lives[0].weather;temperature.value = res.lives[0].temperature;
}// 定义interface
interface IconMap {[key: string]: string;
}
// 计算天气图标类型
const iconMap: IconMap = {晴: "iconfont icon-31qing",多云: "iconfont icon-qingjianduoyun",阴: "iconfont icon-yin","雷阵雨|阵雨|强阵雨|强雷阵雨": "iconfont icon-leizhenyu",雷阵雨并伴有冰雹: "iconfont icon-leizhenyubingbanyoubingbao","小雨|毛毛雨|细雨|中雨|大雨|冻雨": "iconfont icon-xiaoyu","暴雨|大暴雨": "iconfont icon-baoyu","特大暴雨|极端降雨": "iconfont icon-n-dabaoyuzhuantedabaoyu","雨雪天气|雨夹雪|阵雨夹雪": "iconfont icon-leizhenyu","小雪|中雪|大雪|雪": "iconfont icon-xiaoxue",暴雪: "iconfont icon-baoxue",阵雪: "iconfont icon-zhenxue","扬沙|沙尘暴|强沙尘暴": "iconfont icon-qiangshachenbao",浮尘: "iconfont icon-fuchen",霾: "iconfont icon-mai","平静|和风|清风": "iconfont icon-youfeng","有风|微风": "iconfont icon-feng","强风|劲风|疾风|大风|烈风|风暴|狂爆风|飓风|热带风暴":"iconfont icon-redaifengbao",龙卷风: "iconfont icon-longjuanfeng",轻雾: "iconfont icon-wu",热: "iconfont icon-redu",冷: "iconfont icon-leng","浓雾|大雾": "iconfont icon-tianqi-teqiangnongwu",
};function getIconUrl(weather: string) {for (const itemCondition in iconMap) {const regex = new RegExp(itemCondition);if (regex.test(weather)) {return iconMap[itemCondition];}}
}const iconUrl = computed(() => getIconUrl(weather.value));
// 测试
// const iconUrl = computed(() => getIconUrl("阵雪"));onMounted(() => {// 封装优化请求,请求的时候存本地且不过期,就使用本地数据,否则重新请求,//在页面加载调用一下这个函数,不去直接调用getType请求接口//  判断本地有没有数据const cates = localStorage.getItem("locationInfo") || "";// console.log(cates);if (cates) {const { time, code } = JSON.parse(cates);// 判断是否过期if (Date.now() - time > 1000 * 600) {getInfoFn();} else {getWeatherFn(code);console.log("使用本地数据");}}return getInfoFn();
});
</script><style scoped lang="scss">
@import url("@/assets/iconFont/iconfont.css");.weather {position: absolute;display: flex;align-items: center;justify-content: space-between;left: 100px;top: 15px;font-size: 22px;background-image: linear-gradient(to right, #fe6601, #ff953c, #fc741a);-webkit-background-clip: text;-webkit-text-fill-color: transparent;animation: shine 3s ease-in-out infinite;.iconfont {color: #fff !important;margin-left: 10px;font-size: 30px;}@keyframes shine {0% {background-position: 0 0;}100% {background-position: 280px 0;}}img {width: 40px;height: 40px;margin: 0 10px;border-radius: 50%;}
}
</style>

🎉   谢谢观看 :

从零配置完整企业级Vue3 + Ts + Vite 项目,集成(路由、pinia、组件、hooks、全局钩子、全局规约、等......)Vue3 + Ts + Vite + pnpm 项目中集成 —— eslint 、prettier、stylelint、husky、commitizen_彩色之外的博客-CSDN博客搭建VIte + Ts + Vue3项目并集成eslint 、prettier、stylelint、huskyhttps://blog.csdn.net/m0_57904695/article/details/129950163?spm=1001.2014.3001.5502

7730e2bd39d64179909767e1967da702.jpeg

 _______________________________  期待再见  _______________________________ 

 


http://www.ppmy.cn/news/339629.html

相关文章

冥想第八百一十八天

1.明天就该回来了&#xff0c;今天晚上跑了7公里。 2.晚上休息的不好&#xff0c;因为要回家。 3.一周忙起来还是特别快的。 4.感谢父母&#xff0c;感谢朋友&#xff0c;感谢家人&#xff0c;感谢不断进步的自己。

联想拯救者14实现快捷键调屏幕亮度

前言 我的EFI制作完成后&#xff0c;快捷键调节声音大小的功能默认就支持&#xff0c;但是快捷键调整亮度就不支持了。 快捷键调节亮度这样的需求对我来话基本是可有可无。因为鼠标简单点点就可以调节亮度了。Big Sur调节亮度真的方便&#xff0c;鼠标点两下就搞定啦。 点击状…

2022款联想拯救者R7000P和联想小新Pro16 选哪个好

022款联想拯救者R7000P和小新Pro16在配置方面&#xff0c;虽然都是一样的处理器、显卡、内存、硬盘&#xff0c;但是2022款联想拯救者R7000P在处理器性能释放更高&#xff0c;达到了80w性能释放。 屏幕对比&#xff1a; 在屏幕方面&#xff0c;联想拯救者R7000P的屏幕尺寸是15.…

联想拯救者r7000搜索不到wifi?

小编用的是联想拯救者r7000笔记本电脑&#xff0c;最近频繁出现搜索不到wifi的情况&#xff0c;在网上查询发现不少用户都出现了同样的情况&#xff0c;那么一起来看看下面的解决办法吧&#xff01; 方法1)更新无线网卡驱动&#xff1a;在联想官网即可下载驱动列表 &#xff0c…

华为ER3208G3 端口映射

华为ER3208G3 端口映射记录 进入网络设置 > NAT配置 注意&#xff1a;外部地址选择想要映射的公网的wan口&#xff0c;内部地址填想要映射到内网的ip地址 ER3208G3路由的映射除了以上的配置外&#xff0c;还需要在高级配置中开启NAT hairpin功能&#xff0c;否则端口映射…

RTX3060显卡的bug

最近小编在帮别人调代码的时候遇到一个难缠的问题&#xff01;&#xff01;就是在RTX3060显卡中跑pytorch的代码&#xff0c;好家伙&#xff0c;第一次弄这么个问题弄这么久。 在配置高端显卡的cuda版本的时候一定要看好手册办事&#xff0c;RTX3060目前支持CUDA11及以上&#…

华为ensp ar201接口怎么配地址

华为ensp AR201路由器的Ethernet0/0/0接口默认在二层接口模式&#xff0c;不能直接在接口上配置 IP地址&#xff0c;需要将默认的二层接口改为三层接口&#xff0c;可正常进行IP地址。 a.实验时&#xff0c;用华为ensp模拟器工具配置AR201系列路由器&#xff0c;进入到Etherne…

Valens VS2311GF HDBaseT光纤接口

Valens Semiconductor 开发了 VS2311 芯片以实现高质量的多媒体通过单根光纤电缆传输。 VS2311 支持以下媒体类型&#xff1a; 1、未压缩的高清 (HD) 视频内容 2、高保真数字音频 3、100BaseTX 以太网 4、各种控制/数据格式&#xff0c;包括 USB2.0、SPDIF 和 I2S 音频、I2C、R…