当前位置: 首页 > news >正文

PHP使用echarts制作一个很漂亮的天气预报网站(曲线图+实况+未来一周预报)

引言

干净利落的天气特效, echarts中使用到了xAxis.axisLabel. rich, 以为echarts只能输出文字呢, 没想到还可以加入图片, 自定义了天气图标, 可以直接当成天气网站使用, 附线上运行预览效果, 有需要源码的可以联系我

实现效果

网页我已经做好了, ✋🏻 点击在线预览运行后效果

在这里插入图片描述

实现流程

难点在于echarts的 rich里的模板定义和formatter模板输出, echarts默认图标和文字的间距很小, axisLabel设置了行高为30, 页面css使用的tailwind

  • 初始化缓存对象:使用了FileCache缓存类,可设置缓存过期时间。
  • 请求天气接口:如果缓存数据返回false,重新请求天气数据,更新缓存。
  • 设置twig变量值:我这边项目使用了twig模板,和thinkphp模板原理应该差不多, 不熟悉thinkphp。
  • 实况和7日数据:直接使用twig模板变量输出,如湿度:{{ weather.data[0].humidity }}。
  • echarts :因为天气接口字段格式是固定的,所以给echarts变量赋值直接写了0-6的索引值。rich里定义了天气api的9种图标,formatter根据星期几去匹配替换对应的图标和内容

上代码

控制端
// weather get
$cache = FileCache::createCache();
$cache->setCachedir(BASE_PATH);
$json_data = $cache->get('weather_101010100', true);
if (empty($json_data)) {//appid和appsecret请去天气api申请,http://tianqiapi.com/user,注册就可以请求3000次$weather = file_get_contents('http://v1.yiketianqi.com/api?unescape=1&version=v91&appid=你的参数&appsecret=你的参数&ext=life&cityid=101010100');$json_data = json_decode($weather, true);$cache->set('weather_101010100', $json_data, 300);
}
$this->assign['weather'] = $json_data;
模板端
<main class="grid grid-cols-1 lg:grid-cols-3 gap-6"><!-- 温度图表卡片 --><div class="lg:col-span-2 bg-white rounded-xl p-5 card-shadow transition-all duration-300 hover:shadow-lg"><div class="flex justify-between items-center mb-4"><h2 class="text-lg font-semibold text-gray-800">{{ weather.city }}温度趋势图</h2><div class="text-sm text-gray-500 flex items-center"><i class="fa fa-refresh mr-1"></i><span id="updateTime">更新于: {{ weather.update_time }}</span></div></div><div class="h-[350px] md:h-[400px] w-full"><div id="temperatureChart" class="w-full h-full" style="user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); position: relative;"><div style="position: relative; width: 720px; height: 400px; padding: 0px; margin: 0px; border-width: 0px; cursor: default;"><canvas data-zr-dom-id="zr_0" width="1440" height="800" style="position: absolute; left: 0px; top: 0px; width: 720px; height: 400px; user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); padding: 0px; margin: 0px; border-width: 0px;"></canvas></div></div></div></div><!-- 天气信息卡片 --><div class="bg-white rounded-xl p-5 card-shadow transition-all duration-300 hover:shadow-lg"><h2 class="text-lg font-semibold text-gray-800 mb-2">{{ weather.city }}今日天气</h2><div><!-- 今日概览 --><div class="text-center p-4"><div class="flex justify-center items-center space-x-4 my-3"><img class="mb-3" style="width: 45px; height: 45px; " src="/static/icon/{{ weather.data[0].wea_img }}.png"><div><div class="text-3xl font-bold text-gray-800">{{ weather.data[0].tem }}°</div><div class="text-gray-500 text-sm">{{ weather.data[0].wea }}</div></div></div><div class="text-sm text-gray-600">{{ weather.data[0].narrative }}</div></div></div><!--湿度--><div class="flex justify-between items-center p-3 mb-3 bg-gray-50 rounded-xl" data-doubao-line="137" data-doubao-column="21" data-doubao-key="43"><div class="flex items-center text-gray-600" data-doubao-line="138" data-doubao-column="25" data-doubao-key="44"><i class="fa fa-tint text-cool mr-2" data-doubao-line="139" data-doubao-column="29" data-doubao-key="45"></i><span data-doubao-line="140" data-doubao-column="29" data-doubao-key="46">湿度</span></div><span class="font-medium" data-doubao-line="142" data-doubao-column="25" data-doubao-key="47">{{ weather.data[0].humidity }}</span></div><!--./湿度--><!--紫外线--><div class="flex justify-between items-center p-3 mb-3 bg-gray-50 rounded-xl" data-doubao-line="153" data-doubao-column="21" data-doubao-key="53"><div class="flex items-center text-gray-600" data-doubao-line="154" data-doubao-column="25" data-doubao-key="54"><i class="fa fa-umbrella text-secondary mr-2" data-doubao-line="155" data-doubao-column="29" data-doubao-key="55"></i><span data-doubao-line="156" data-doubao-column="29" data-doubao-key="56">紫外线</span></div><span class="font-medium text-accent" data-doubao-line="158" data-doubao-column="25" data-doubao-key="57">{{ weather.data[0].uvDescription }}</span></div><!--./紫外线--><!--item3--><div class="flex justify-between items-center p-3 mb-3 bg-gray-50 rounded-xl" data-doubao-line="161" data-doubao-column="21" data-doubao-key="58"><div class="flex items-center text-gray-600" data-doubao-line="162" data-doubao-column="25" data-doubao-key="59"><i class="fa fa-cloud text-neutral mr-2" data-doubao-line="163" data-doubao-column="29" data-doubao-key="60"></i><span data-doubao-line="164" data-doubao-column="29" data-doubao-key="61">能见度</span></div><span class="font-medium" data-doubao-line="166" data-doubao-column="25" data-doubao-key="62">{{ weather.data[0].visibility }}</span></div><!--./item3--><!-- 提示 --><div class="p-3 bg-blue-50 rounded-lg border border-blue-100"><h3 class="font-medium text-primary mb-2 flex items-center"><i class="fa fa-lightbulb-o mr-2"></i>温馨提示</h3><p class="text-sm text-gray-600">{{ weather.data[0].index[3].desc }}</p></div><!-- ./提示 --></div><!-- 7日天气列表 --><div class="lg:col-span-3 bg-white rounded-xl p-5 mb-6 card-shadow transition-all duration-300 hover:shadow-lg"><h2 class="text-lg font-semibold text-gray-800 mb-4">{{ weather.city }}未来天气</h2><div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-7 gap-4"><!-- 每日天气卡片 - 动态生成 --><div class="bg-gray-50 rounded-lg p-4 text-center hover:bg-gray-100 transition-colors duration-200"><div class="font-medium">{{ weather.data[0].date }}</div><div class="text-sm text-gray-500 mb-2">{{ weather.data[0].week }}</div><img class="mb-3" style="width: 30px; height: 30px; margin: 0 auto 0.75rem;" src="/static/icon/{{ weather.data[0].wea_img }}.png"><div class="text-sm text-gray-600 mb-2">{{ weather.data[0].wea }}</div><div class="flex justify-center items-center gap-2"><span class="text-warm">{{ weather.data[0].tem2 }}°</span><span class="text-gray-300">/</span><span class="text-cool">{{ weather.data[0].tem1 }}°</span></div><div class="text-xs text-gray-500 mt-2"><i class="fa fa-tint mr-1"></i>{{ weather.data[0].humidity }}</div><div class="text-xs text-gray-500"><i class="fa fa-wind mr-1"></i>{{ weather.data[0].win[0] }}{{ weather.data[0].win_speed }}</div></div><div class="bg-gray-50 rounded-lg p-4 text-center hover:bg-gray-100 transition-colors duration-200"><div class="font-medium">{{ weather.data[1].date }}</div><div class="text-sm text-gray-500 mb-2">{{ weather.data[1].week }}</div><img class="mb-1" style="width: 30px; height: 30px; margin: 0 auto 0.75rem;" src="/static/icon/{{ weather.data[1].wea_img }}.png"><div class="text-sm text-gray-600 mb-2">{{ weather.data[1].wea }}</div><div class="flex justify-center items-center gap-2"><span class="text-warm">{{ weather.data[1].tem2 }}°</span><span class="text-gray-300">/</span><span class="text-cool">{{ weather.data[1].tem1 }}°</span></div><div class="text-xs text-gray-500 mt-2"><i class="fa fa-tint mr-1"></i>{{ weather.data[1].humidity }}</div><div class="text-xs text-gray-500"><i class="fa fa-wind mr-1"></i>{{ weather.data[1].win[0] }}{{ weather.data[1].win_speed }}</div></div><div class="bg-gray-50 rounded-lg p-4 text-center hover:bg-gray-100 transition-colors duration-200"><div class="font-medium">{{ weather.data[2].date }}</div><div class="text-sm text-gray-500 mb-2">{{ weather.data[2].week }}</div><img class="mb-1" style="width: 30px; height: 30px; margin: 0 auto 0.75rem;" src="/static/icon/{{ weather.data[2].wea_img }}.png"><div class="text-sm text-gray-600 mb-2">{{ weather.data[2].wea }}</div><div class="flex justify-center items-center gap-2"><span class="text-warm">{{ weather.data[2].tem2 }}°</span><span class="text-gray-300">/</span><span class="text-cool">{{ weather.data[2].tem1 }}°</span></div><div class="text-xs text-gray-500 mt-2"><i class="fa fa-tint mr-1"></i>{{ weather.data[2].humidity }}</div><div class="text-xs text-gray-500"><i class="fa fa-wind mr-1"></i>{{ weather.data[2].win[0] }}{{ weather.data[2].win_speed }}</div></div><div class="bg-gray-50 rounded-lg p-4 text-center hover:bg-gray-100 transition-colors duration-200"><div class="font-medium">{{ weather.data[3].date }}</div><div class="text-sm text-gray-500 mb-2">{{ weather.data[3].week }}</div><img class="mb-1" style="width: 30px; height: 30px; margin: 0 auto 0.75rem;" src="/static/icon/{{ weather.data[3].wea_img }}.png"><div class="text-sm text-gray-600 mb-2">{{ weather.data[3].wea }}</div><div class="flex justify-center items-center gap-2"><span class="text-warm">{{ weather.data[3].tem2 }}°</span><span class="text-gray-300">/</span><span class="text-cool">{{ weather.data[3].tem1 }}°</span></div><div class="text-xs text-gray-500 mt-2"><i class="fa fa-tint mr-1"></i>{{ weather.data[3].humidity }}</div><div class="text-xs text-gray-500"><i class="fa fa-wind mr-1"></i>{{ weather.data[3].win[0] }}{{ weather.data[3].win_speed }}</div></div><div class="bg-gray-50 rounded-lg p-4 text-center hover:bg-gray-100 transition-colors duration-200"><div class="font-medium">{{ weather.data[4].date }}</div><div class="text-sm text-gray-500 mb-2">{{ weather.data[4].week }}</div><img class="mb-1" style="width: 30px; height: 30px; margin: 0 auto 0.75rem;" src="/static/icon/{{ weather.data[4].wea_img }}.png"><div class="text-sm text-gray-600 mb-2">{{ weather.data[4].wea }}</div><div class="flex justify-center items-center gap-2"><span class="text-warm">{{ weather.data[4].tem2 }}°</span><span class="text-gray-300">/</span><span class="text-cool">{{ weather.data[4].tem1 }}°</span></div><div class="text-xs text-gray-500 mt-2"><i class="fa fa-tint mr-1"></i>{{ weather.data[4].humidity }}</div><div class="text-xs text-gray-500"><i class="fa fa-wind mr-1"></i>{{ weather.data[4].win[0] }}{{ weather.data[4].win_speed }}</div></div><div class="bg-gray-50 rounded-lg p-4 text-center hover:bg-gray-100 transition-colors duration-200"><div class="font-medium">{{ weather.data[5].date }}</div><div class="text-sm text-gray-500 mb-2">{{ weather.data[5].week }}</div><img class="mb-1" style="width: 30px; height: 30px; margin: 0 auto 0.75rem;" src="/static/icon/{{ weather.data[5].wea_img }}.png"><div class="text-sm text-gray-600 mb-2">{{ weather.data[5].wea }}</div><div class="flex justify-center items-center gap-2"><span class="text-warm">{{ weather.data[5].tem2 }}°</span><span class="text-gray-300">/</span><span class="text-cool">{{ weather.data[5].tem1 }}°</span></div><div class="text-xs text-gray-500 mt-2"><i class="fa fa-tint mr-1"></i>{{ weather.data[5].humidity }}</div><div class="text-xs text-gray-500"><i class="fa fa-wind mr-1"></i>{{ weather.data[5].win[0] }}{{ weather.data[5].win_speed }}</div></div><div class="bg-gray-50 rounded-lg p-4 text-center hover:bg-gray-100 transition-colors duration-200"><div class="font-medium">{{ weather.data[6].date }}</div><div class="text-sm text-gray-500 mb-2">{{ weather.data[6].week }}</div><img class="mb-1" style="width: 30px; height: 30px; margin: 0 auto 0.75rem;" src="/static/icon/{{ weather.data[6].wea_img }}.png"><div class="text-sm text-gray-600 mb-2">{{ weather.data[6].wea }}</div><div class="flex justify-center items-center gap-2"><span class="text-warm">{{ weather.data[6].tem2 }}°</span><span class="text-gray-300">/</span><span class="text-cool">{{ weather.data[6].tem1 }}°</span></div><div class="text-xs text-gray-500 mt-2"><i class="fa fa-tint mr-1"></i>{{ weather.data[6].humidity }}</div><div class="text-xs text-gray-500"><i class="fa fa-wind mr-1"></i>{{ weather.data[6].win[0] }}{{ weather.data[6].win_speed }}</div></div></div></div>
</main>
JS部分
document.addEventListener('DOMContentLoaded', function() {initTemperatureChart();
});
// 未来7天的天气数据
const weatherData = {days: ['{{ weather.data[0].week }}', '{{ weather.data[1].week }}', '{{ weather.data[2].week }}', '{{ weather.data[3].week }}', '{{ weather.data[4].week }}', '{{ weather.data[5].week }}', '{{ weather.data[6].week }}'],dates: ['{{ weather.data[0].date }}', '{{ weather.data[1].date }}', '{{ weather.data[2].date }}', '{{ weather.data[3].date }}', '{{ weather.data[4].date }}', '{{ weather.data[5].date }}', '{{ weather.data[6].date }}'],highTemp: [{{ weather.data[0].tem1 }}, {{ weather.data[1].tem1 }}, {{ weather.data[2].tem1 }}, {{ weather.data[3].tem1 }}, {{ weather.data[4].tem1 }}, {{ weather.data[5].tem1 }}, {{ weather.data[6].tem1 }}],lowTemp: [{{ weather.data[0].tem2 }}, {{ weather.data[1].tem2 }}, {{ weather.data[2].tem2 }}, {{ weather.data[3].tem2 }}, {{ weather.data[4].tem2 }}, {{ weather.data[5].tem2 }}, {{ weather.data[6].tem2 }}],weather: ['{{ weather.data[0].wea }}', '{{ weather.data[1].wea }}', '{{ weather.data[2].wea }}', '{{ weather.data[3].wea }}', '{{ weather.data[4].wea }}', '{{ weather.data[5].wea }}', '{{ weather.data[6].wea }}']
};
// 初始化ECharts图表
function initTemperatureChart() {// 获取图表容器const chartDom = document.getElementById('temperatureChart');const myChart = echarts.init(chartDom);// 配置图表const option = {backgroundColor: 'transparent',tooltip: {trigger: 'axis',backgroundColor: 'rgba(255, 255, 255, 0.9)',borderColor: '#eee',borderWidth: 1,textStyle: { color: '#333' },formatter: function(params) {let param = params[0];return `<div class="font-medium">${weatherData.dates[param.dataIndex]}</div><div>最高温度: ${weatherData.highTemp[param.dataIndex]}°C</div><div>最低温度: ${weatherData.lowTemp[param.dataIndex]}°C</div><div>天气: ${weatherData.weather[param.dataIndex]}</div>`;}},legend: {data: ['最高温度', '最低温度'],top: 0,textStyle: { color: '#666' }},grid: {left: '3%',right: '4%',bottom: '3%',containLabel: true},xAxis: [{type: 'category',boundaryGap: false,data: weatherData.days.map((day, i) => `${weatherData.days[i]}`),axisLine: {lineStyle: {color: '#ddd'}},position: 'top',axisLabel: {lineHeight: 30,color: '#666',rich: {// 模板1:美食图标(本地/在线图片均可,此处用在线图标)icon_qing: {width: 25, // 图标宽度height: 25, // 图标高度backgroundColor: {image: '/static/icon/qing.png' // 图标地址}},icon_yun: {width: 25, // 图标宽度height: 25, // 图标高度backgroundColor: {image: '/static/icon/yun.png' // 图标地址}},icon_yin: {width: 25, // 图标宽度height: 25, // 图标高度backgroundColor: {image: '/static/icon/yin.png' // 图标地址}},icon_lei: {width: 25, // 图标宽度height: 25, // 图标高度backgroundColor: {image: '/static/icon/lei.png' // 图标地址}},icon_xue: {width: 25, // 图标宽度height: 25, // 图标高度backgroundColor: {image: '/static/icon/xue.png' // 图标地址}},icon_shachen: {width: 25, // 图标宽度height: 25, // 图标高度backgroundColor: {image: '/static/icon/shachen.png' // 图标地址}},icon_wu: {width: 25, // 图标宽度height: 25, // 图标高度backgroundColor: {image: '/static/icon/wu.png' // 图标地址}},icon_bingbao: {width: 25, // 图标宽度height: 25, // 图标高度backgroundColor: {image: '/static/icon/bingbao.png' // 图标地址}},icon_yu: {width: 25, // 图标宽度height: 25, // 图标高度backgroundColor: {image: '/static/icon/yu.png' // 图标地址}}},// 2. 调用 rich 模板,组合“图标+文字”formatter: function(value) {// 根据 X 轴数据(value)匹配对应的图标模板switch (value) {case '{{ weather.data[0].week }}':return '{{ weather.data[0].date }}\n{icon_{{ weather.data[0].wea_img }}|}\n{{ weather.data[0].wea }}\n';case '{{ weather.data[1].week }}':return '{{ weather.data[1].date }}\n{icon_{{ weather.data[1].wea_img }}|}\n{{ weather.data[1].wea }}\n';case '{{ weather.data[2].week }}':return '{{ weather.data[2].date }}\n{icon_{{ weather.data[2].wea_img }}|}\n{{ weather.data[2].wea }}\n';case '{{ weather.data[3].week }}':return '{{ weather.data[3].date }}\n{icon_{{ weather.data[3].wea_img }}|}\n{{ weather.data[3].wea }}\n';case '{{ weather.data[4].week }}':return '{{ weather.data[4].date }}\n{icon_{{ weather.data[4].wea_img }}|}\n{{ weather.data[4].wea }}\n';case '{{ weather.data[5].week }}':return '{{ weather.data[5].date }}\n{icon_{{ weather.data[5].wea_img }}|}\n{{ weather.data[5].wea }}\n';case '{{ weather.data[6].week }}':return '{{ weather.data[6].date }}\n{icon_{{ weather.data[6].wea_img }}|}\n{{ weather.data[6].wea }}\n';default:return value;}},// 标签横向对齐(避免图标偏移)align: 'center'}
}, {type: 'category',boundaryGap: false,data: weatherData.days.map((day, i) => `${weatherData.days[i]}`),axisLine: {lineStyle: {color: '#ddd'}},position: 'bottom',axisLabel: {lineHeight: 20,color: '#666'}}, ],yAxis: {type: 'value',name: '',nameTextStyle: { color: '#666' },axisLine: {lineStyle: { color: '#ddd' }},splitLine: {lineStyle: { color: '#f0f0f0' }},axisLabel: {formatter: '{value}',color: '#666'},min: function(value) {return value.min - 2; // 最小值向下调整2度},max: function(value) {return value.max + 2; // 最大值向上调整2度}},series: [{name: '最高温度',type: 'line',data: weatherData.highTemp,symbol: 'circle',symbolSize: 8,lineStyle: {width: 3,color: '#F56C6C' // 暖色调},itemStyle: {color: '#F56C6C',borderWidth: 2,borderColor: '#fff',shadowBlur: 4,shadowColor: 'rgba(245, 108, 108, 0.5)'},emphasis: {scale: true,symbolSize: 10},areaStyle: {color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: 'rgba(245, 108, 108, 0.3)' },{ offset: 1, color: 'rgba(245, 108, 108, 0)' }])}},{name: '最低温度',type: 'line',data: weatherData.lowTemp,symbol: 'circle',symbolSize: 8,lineStyle: {width: 3,color: '#4E5BA6' // 冷色调},itemStyle: {color: '#4E5BA6',borderWidth: 2,borderColor: '#fff',shadowBlur: 4,shadowColor: 'rgba(78, 91, 166, 0.5)'},emphasis: {scale: true,symbolSize: 10},areaStyle: {color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: 'rgba(78, 91, 166, 0.3)' },{ offset: 1, color: 'rgba(78, 91, 166, 0)' }])}}]
};// 设置图表配置项myChart.setOption(option);// 响应窗口大小变化window.addEventListener('resize', function() {myChart.resize();});
}

文章转载自:

http://btsSEIOW.xwgbr.cn
http://3Cjl1bTM.xwgbr.cn
http://SU4AMJyh.xwgbr.cn
http://v53YkocA.xwgbr.cn
http://sWOJll4u.xwgbr.cn
http://2pf0jW0n.xwgbr.cn
http://Kgco9NQK.xwgbr.cn
http://Ua65buSW.xwgbr.cn
http://F16fqgzo.xwgbr.cn
http://5RVdBA6Z.xwgbr.cn
http://S5dgYdmy.xwgbr.cn
http://Vvvu3WNF.xwgbr.cn
http://MDHPxsHc.xwgbr.cn
http://54DJwY16.xwgbr.cn
http://io31u4an.xwgbr.cn
http://C5ocXjEy.xwgbr.cn
http://FuLz7nlA.xwgbr.cn
http://yhzQX8w9.xwgbr.cn
http://CbRsSuGY.xwgbr.cn
http://rSliaAPH.xwgbr.cn
http://3Hb2Nv7D.xwgbr.cn
http://l80Fa88C.xwgbr.cn
http://9fD6Ild6.xwgbr.cn
http://QOR9LiC4.xwgbr.cn
http://9ovev819.xwgbr.cn
http://CmzR6cGa.xwgbr.cn
http://92AYtmWR.xwgbr.cn
http://a9BYBPRg.xwgbr.cn
http://7P288j8a.xwgbr.cn
http://FoT5Yhdt.xwgbr.cn
http://www.dtcms.com/a/382720.html

相关文章:

  • 数据库造神计划第九天---增删改查(CRUD)(5)
  • 简单的折叠cell
  • 贪心算法在边缘计算卸载问题中的应用
  • pyAutoGUI 模块主要功能介绍-(2)键盘功能
  • 基于Qt Creator的Serial Port串口调试助手项目(代码开源)
  • Node.js 编码规范
  • Spring Boot 调度任务在分布式环境下的坑:任务重复执行与一致性保证
  • 【数据结构】 ArrayList深入解析
  • 4. 数系
  • 08 函数式编程
  • 安卓 Google Maps 的使用和开发步骤
  • 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第十三章知识点问答(15题)
  • 深入理解 Spring @Async 注解:原理、实现与实践
  • 【Qt开发】显示类控件(三)-> QProgressBar
  • 《Linux——gflags》
  • leetcode35.搜索插入位置
  • Java调用UniHttp接口请求失败?一次开源的深入实践-百度SN签名认证场景下参数乱序问题的三种解决策略
  • MongoDB 监控
  • 【Linux】system V共享内存
  • --- 统一请求入口 Gateway ---
  • 豆包Seedream 4.0多图融合实力派:田园犬+三花猫多场景创作,AI绘画新时代来了!
  • 贪心算法应用:数据包调度问题详解
  • html基本知识
  • 视觉SLAM第10讲:后端2(滑动窗口与位子图优化)
  • Modbus协议原理与Go语言实现详解
  • 模型部署|将自己训练的yolov8模型在rk3568上部署
  • Vue中的slot标签——插槽
  • k8s集群—node节点的删除与添加
  • k8s的dashboard
  • k8s-容器探针和生命周期回调学习