封装函数,随机背景

This commit is contained in:
2025-12-09 23:55:57 +08:00
parent 2066dfe8c1
commit 8c11d3a0de
4 changed files with 258 additions and 161 deletions

View File

@@ -1,10 +1,15 @@
<template>
<div class="bigscreen">
<div class="bigscreen" :style="{
backgroundImage: bgimg ? `url(${bgimg})` : '',
backgroundSize: 'cover',
backgroundPosition: 'center',
transition: 'background-image 0.5s',
}">
<header>
<h2>全国GDP</h2>
</header>
<main>
<left class="left">
<div class="left">
<div class="first">
<chartContainer>
<div class="data" v-if="GDPgrowth && GDPtotal">
@@ -12,7 +17,6 @@
<p>同比增长{{ GDPgrowth }}%</p>
</div>
</chartContainer>
</div>
<div class="top_five">
<chart-container>
@@ -22,38 +26,73 @@
<!-- //人均 -->
<div class="peopleGDP" ref="PoepleTop5Ref"></div>
</left>
<middle>
<div class="china_map" ref="chinaMapRef">
</div>
<div class="middle">
<div class="china_map" ref="chinaMapRef"></div>
</div>
<div class="right">
<div class="weather">
<!-- 简单天气组件 -->
<chartContainer>
<div class="weather-info" v-if="weather">
<span style="color: red">我tm真是为了一碟醋 包了一盘饺子</span>
<p>城市{{ weather.city }}</p>
<p>天气{{ weather.weather }}</p>
<p>当前温度{{ weather.temperature }}°C</p>
<p>湿度{{ weather.humidity }}%</p>
<p>风向{{ weather.winddirection }}</p>
<p>风力{{ weather.windpower }}</p>
</div>
</chartContainer>
</div>
</middle>
<right class="right">
<div class="weather"></div>
<div class="CountryGDP"></div>
<div class="message"></div>
</right>
</div>
</main>
</div>
<div id="container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { initChinaMap } from './ts/initChinaMap';
import { getData } from '@/utils/getData';
import { useCityGDPStore } from '@/stores/cityGDP';
import { get } from '@/utils/request';
import chartContainer from './components/chartContainer.vue';
import { initTop5 } from './ts/initTop5';
import AMapLoader from "@amap/amap-jsapi-loader";
import { ref, onMounted, onBeforeUnmount } from "vue";
import { initChinaMap } from "./ts/initChinaMap";
import { getData } from "@/utils/getData";
import { useCityGDPStore } from "@/stores/cityGDP";
import { get } from "@/utils/request";
import chartContainer from "./components/chartContainer.vue";
import { initTop5 } from "./ts/initTop5";
import getweather from "./ts/getweather";
// 全局状态
const cityGDPStore = useCityGDPStore();
// dom节点
const chinaMapRef = ref<HTMLDivElement>();
const CityTop5Ref = ref<HTMLDivElement>();
const PoepleTop5Ref = ref<HTMLDivElement>();
//经纬度变量
let lng: number, lat: number, city: number;
const weather = ref<any>(sessionStorage.getItem("weather_cache_v1"));
// 随机背景图相关
const bgimg = ref<string>("");
const bgtime = ref<number>(0);
const BG_CACHE_KEY = "bgimg";
const BG_TIME_KEY = "bgtime";
const BG_TTL = 2 * 60 * 1000; // 2分钟
function getBg() {
const now = Date.now();
fetch(
"http://jycdt.cn:54321/?token=ZxoWsYjxVWqb69jHLadvaAnZ8F38U6t35ALuuXF7wQwm9UtCuhCypc6z0997oit0qCkppWZqbffjqeVTCnsoE5twzhx0MVfz68p9"
)
.then((res) => res.json())
.then((res) => {
bgimg.value = res.img;
bgtime.value = now;
try {
localStorage.setItem(BG_CACHE_KEY, res.img);
localStorage.setItem(BG_TIME_KEY, String(now));
} catch (e) {
console.warn("localStorage 写入失败", e);
}
});
}
// 为高德地图安全配置添加类型声明
declare global {
interface Window {
@@ -66,155 +105,82 @@ declare global {
const GDPtotal = ref<number>(0);
const GDPtotalLast = ref<number>(0);
const GDPgrowth = ref<number>(0);
//计算GDP
// 计算GDP
const calcGDP = () => {
GDPtotal.value = cityGDPStore.cityGDP!.reduce((add, item: any) => {
return add + item.gdp
return add + item.gdp;
}, 0);
GDPtotalLast.value = cityGDPStore.cityGDP!.reduce((add, item: any) => {
return add + item.last
return add + item.last;
}, 0);
const GDPgrowthTmp = (GDPtotal.value - GDPtotalLast.value) / GDPtotalLast.value * 10000;
const GDPgrowthTmp =
((GDPtotal.value - GDPtotalLast.value) / GDPtotalLast.value) * 10000;
GDPgrowth.value = Math.ceil(GDPgrowthTmp) / 100;
};
//地理位置逆编码
const getLocation = async (lng: number, lat: number, key: string) => {
const url: string = "https://restapi.amap.com"
const checkTime = (): boolean => {
const now = new Date().getTime();
const cd = 1000 * 60
//会话存储取出json
const location: { time: number, citycode: string } = JSON.parse(sessionStorage.getItem('location')!);
if (!location) {
console.log("开始请求");
return true
}
const _time = now - location.time
if (_time > cd) {
console.log("开始请求");
return true
}
console.log(_time, now, location);
console.log(`剩余cd${(cd - _time) / 1000}s`);
return false
}
if (lng && lat && checkTime()) {
const res = await get<any>(`${url}/v3/geocode/regeo?key=${key}&location=${lng},${lat}`);
console.log(res);
if (res.status === '1') {
const time = new Date().getTime()
let data = {}
data = {
time,
citycode: res.regeocode.addressComponent.adcode,
}
//转字符串存入会话存储
sessionStorage.setItem('location', JSON.stringify(data))
} else {
console.log("地理逆编码错误");
console.log(res);
}
let adcode: any = sessionStorage.getItem("location")
adcode = JSON.parse(adcode)
adcode = adcode.citycode
const weather = await get(`${url}/v3/weather/weatherInfo?key=${key}&city=${adcode}`)
console.log(weather);
}
return null;
};
const initAMap = async (key1: string, key2: string, key3: string) => {
window._AMapSecurityConfig = {
securityJsCode: key2,
};
return new Promise<void>((resolve) => {
AMapLoader.load({
key: key1,
version: "2.0",
plugins: ["AMap.Scale", "AMap.Geolocation"],
}).then((AMap) => {
function onComplete(data: any) {
console.log("定位成功!");
lng = data.position.lng;
lat = data.position.lat;
// 调用逆地理编码获取位置详情
getLocation(lng, lat, key3).then(() => {
resolve();
});
}
function onError(data: any) {
console.log(`定位失败:${data.message}`);
resolve(); // 即使定位失败也继续执行
}
AMap.plugin('AMap.Geolocation', function () {
const geolocation = new AMap.Geolocation({
enableHighAccuracy: true,
timeout: 1000000,
buttonPosition: "RB",
});
geolocation.getCurrentPosition(function (status: any, result: any) {
if (status == 'complete') {
onComplete(result);
} else {
onError(result);
}
});
});
}).catch(() => {
resolve(); // 加载失败也继续执行
});
});
};
onMounted(async () => {
//地图传入key安全密钥定位服务key
await initAMap("fe533a0fc1658158460a31b3ae0add51", "43029a44482fc7cdf66b0b590d138278", "48da806104e4bf77be4eeecf83f440aa"); // 初始化高德地图安全配置
// getLocation(120.126893, 30.276614, "48da806104e4bf77be4eeecf83f440aa")
// 背景图缓存逻辑
const cachedImg = localStorage.getItem(BG_CACHE_KEY);
const cachedTime = Number(localStorage.getItem(BG_TIME_KEY) || 0);
if (cachedImg) bgimg.value = cachedImg;
if (cachedTime) bgtime.value = cachedTime;
// 判断是否过期
if (!bgimg.value || Date.now() - bgtime.value > BG_TTL) {
getBg();
}
await getweather(
"fe533a0fc1658158460a31b3ae0add51",
"43029a44482fc7cdf66b0b590d138278",
"48da806104e4bf77be4eeecf83f440aa"
); //天气存储在sessionStorage
await getData(); // 获取数据
calcGDP(); // 计算GDP
await initChinaMap(chinaMapRef.value!); // 初始化地图
await initTop5(CityTop5Ref.value!, 1);
await initTop5(PoepleTop5Ref.value!, 2);
weather.value = JSON.parse(
sessionStorage.getItem("weather_cache_v1")!
).data!.weather!.lives[0];
});
</script>
<style lang="less" scoped>
//control_min
@main-bg: #9fff7c;
@control-min-width: 800px;
//header
@header-height: 10vh;
// @header-bg: rgb(70, 244, 244);
@header-bg: rgb(255, 255, 255);
@header-bg: @main-bg;
// @header-bg: rgb(255, 255, 255);
//main
@main-height: 90vh;
// @main-bg: rgb(103, 255, 131);
@main-bg: rgb(255, 255, 255);
// @main-bg: rgb(255, 255, 255);
//left
@left-width: 30vw;
// @left-bg: rgb(237, 70, 70);
@left-bg: rgb(255, 255, 255);
@left-bg: @main-bg;
// @left-bg: rgb(255, 255, 255);
//middle
@middle-width: 40vw;
// @middle-bg: rgb(83, 123, 254);
@middle-bg: rgb(255, 255, 255);
@middle-bg: @main-bg;
// @middle-bg: rgb(255, 255, 255);
//right
@right-width: 30vw;
// @right-bg: rgb(255, 60, 213);
@right-bg: rgb(255, 255, 255);
@right-bg: @main-bg;
// @right-bg: rgb(255, 255, 255);
#container {
visibility: hidden;
display: none;
}
.bigscreen {
height: 100vh;
width: 100vw;
color: white;
header {
height: @header-height;
background: @header-bg;
// background: @header-bg;
@media screen and (width<@control-min-width) {
width: @control-min-width;
@@ -229,15 +195,13 @@ onMounted(async () => {
main {
height: @main-height;
background-color: @main-bg;
/* background-color: @main-bg; */
display: flex;
flex-direction: row;
left,
right {
.left {
width: @left-width;
background-color: @left-bg;
/* background-color: @left-bg; */
display: flex;
flex-direction: column;
justify-content: space-between;
@@ -246,7 +210,6 @@ onMounted(async () => {
>* {
height: 32%;
width: calc(100% - 10px);
}
.top5 {
@@ -255,14 +218,9 @@ onMounted(async () => {
}
}
right {
width: @right-width;
background-color: @right-bg;
}
middle {
.middle {
width: @middle-width;
background-color: @middle-bg;
/* background-color: @middle-bg; */
overflow: hidden;
min-width: 320px;
@@ -276,10 +234,19 @@ onMounted(async () => {
}
}
right {
.right {
width: @right-width;
background-color: @right-bg;
/* background-color: @right-bg; */
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
>* {
width: 100%;
margin-bottom: 10px;
}
}
}
}
</style>
</style>

View File

@@ -4,7 +4,7 @@
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ref, onMounted } from "vue";
interface Props {
width?: any;
@@ -24,5 +24,11 @@ const chart = ref(null);
flex-direction: column;
box-sizing: border-box;
padding: 0 5px;
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.12);
border-radius: 18px;
background: rgba(0, 0, 0, 0.331);
backdrop-filter: blur(12px) saturate(180%);
-webkit-backdrop-filter: blur(12px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.25);
}
</style>
</style>

115
src/page/ts/getweather.ts Normal file
View File

@@ -0,0 +1,115 @@
/**
* 获取天气信息:先使用高德 JSAPI 定位 -> 逆地理获取城市/行政区 -> 调用高德天气 WebAPI
* @param key1 JSAPI key (用于 AMapLoader)
* @param key2 安全码 securityJsCode
* @param key3 Web API key (用于天气接口)
* @returns Promise<{ position: {lng, lat}, address: string, weather: any }>
* 存储位置sessionStorage.weather_cache_v1
*/
import AMapLoader from '@amap/amap-jsapi-loader'
const url = 'https://restapi.amap.com'
// 为高德地图安全配置添加类型声明
declare global {
interface Window {
_AMapSecurityConfig: {
securityJsCode: string
}
}
}
async function fetchJson(u: string) {
const res = await fetch(u)
return res.json()
}
async function getweather(key1: string, key2: string, key3: string): Promise<any> {
// 缓存 key
const CACHE_KEY = 'weather_cache_v1'
const CACHE_TTL = 10 * 60 * 1000 // 10分钟
// 检查缓存
const cacheRaw = sessionStorage.getItem(CACHE_KEY)
if (cacheRaw) {
try {
const cache = JSON.parse(cacheRaw)
if (cache.time && Date.now() - cache.time < CACHE_TTL) {
return Promise.resolve(cache.data)
}
} catch {}
}
return new Promise<any>(async (resolve, reject) => {
try {
// 设置安全码
window._AMapSecurityConfig = { securityJsCode: key2 }
// 加载 AMap并确保需要的插件Geolocation + Geocoder
const AMap = await AMapLoader.load({
key: key1,
version: '2.0',
plugins: ['AMap.Geolocation', 'AMap.Geocoder'],
})
// 创建定位实例并获取当前位置
const geolocation = new AMap.Geolocation({
enableHighAccuracy: true,
timeout: 10000,
})
geolocation.getCurrentPosition(async (status: string, result: any) => {
if (status !== 'complete' || !result) {
return reject(new Error('定位失败'))
}
// result.position 通常是 {lng, lat}
const pos =
result.position ||
result.lnglat ||
(result?.coords ? { lng: result.coords.lng, lat: result.coords.lat } : null)
if (!pos) return reject(new Error('无法获取坐标信息'))
// 使用 Geocoder 逆地理获取城市或 adcode
const geocoder = new AMap.Geocoder()
geocoder.getAddress([pos.lng, pos.lat], async (status2: string, res2: any) => {
if (status2 !== 'complete' || !res2 || !res2.regeocode) {
return reject(new Error('逆地理解析失败'))
}
const addrComp = res2.regeocode.addressComponent || {}
const city =
addrComp.city && addrComp.city.length ? addrComp.city : addrComp.province || ''
const adcode = addrComp.adcode || ''
// 优先使用 adcode更加精确否则使用 city 名称
const cityParam = adcode || city
// 调用高德天气接口返回实时或预报信息extensions=all 返回更多字段)
const weatherUrl = `${url}/v3/weather/weatherInfo?key=${encodeURIComponent(key3)}&city=${encodeURIComponent(
cityParam,
)}&extensions=base&output=json`
try {
const weather = await fetchJson(weatherUrl)
const data = {
position: { lng: pos.lng, lat: pos.lat },
address: res2.regeocode.formattedAddress,
weather,
}
// 写入缓存
sessionStorage.setItem(CACHE_KEY, JSON.stringify({ time: Date.now(), data }))
resolve(data)
} catch (err) {
reject(err)
}
})
})
} catch (err) {
reject(err)
}
})
}
export default getweather

View File

@@ -18,23 +18,22 @@ export const initTop5 = async (Top5Ref: HTMLDivElement, type: number) => {
CityGDP.push(item.gdp)
}
if (type == 2) {
const r = Number(item.gdp) / Number(item.people)
const r = (Number(item.gdp) / Number(item.people)).toFixed(2)
CityGDP.push(String(r))
}
})
let option: EChartsOption = {
// grid: {
// right: 80, // 为右侧标签留出足够空间
// left: 0, // 确保左侧城市名称有足够的显示空间
// top: 0,
// bottom: 0,
// },
textStyle: { color: '#fff' },
title: {
text: '',
left: 'center',
textStyle: { color: '#fff' },
},
xAxis: {
max: 'dataMax',
axisLabel: { color: '#fff' },
axisLine: { lineStyle: { color: '#fff' } },
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.2)' } },
},
yAxis: {
type: 'category',
@@ -43,6 +42,8 @@ export const initTop5 = async (Top5Ref: HTMLDivElement, type: number) => {
animationDuration: 300,
animationDurationUpdate: 300,
max: 4, // only the largest 3 bars will be displayed
axisLabel: { color: '#fff' },
axisLine: { lineStyle: { color: '#fff' } },
},
series: [
{
@@ -55,16 +56,24 @@ export const initTop5 = async (Top5Ref: HTMLDivElement, type: number) => {
position: 'right',
valueAnimation: true,
fontSize: 14,
color: '#fff',
// 添加文本溢出处理
overflow: 'truncate',
width: 100,
},
itemStyle: { color: '#74add1' },
},
],
legend: {
show: false,
textStyle: { color: '#fff' },
},
tooltip: {
show: true,
// backgroundColor: 'rgba(0,0,0,0.7)',
// borderColor: '#fff',
// textStyle: { color: '#fff' },
},
tooltip: { show: true },
animationDuration: 0,
animationDurationUpdate: 3000,
animationEasing: 'linear',