封装函数,随机背景
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
115
src/page/ts/getweather.ts
Normal 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
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user