Skip to content
0

配置首页欢迎语-天气-位置-舔狗日记-v2

配置首页欢迎语-天气-位置-舔狗日记

存在问题

这个api无法正常提供服务了。。。

image-20250917214945410

豆包提问

问题1-可替换方案

提出问题

bash
<script setup lang="ts">
import { onMounted, ref,h } from 'vue';
import { TkMessage } from "vitepress-theme-teek";

// 天气数据
const weatherData = ref({
  city: '',
  temperature: '',
  type: '',
  date: '',
  week: ''
});

// 获取天气信息的函数
const error = ref(false);
const loading = ref(false); // 控制加载中状态
// 获取天气信息的函数
const getWeatherInfo = async () => {
  loading.value = true; // 开始加载
  try {
    const response = await fetch('https://api.vvhan.com/api/weather');
    const data = await response.json();
    if (data.success) {
      weatherData.value = {
        city: data.city,
        temperature: `${data.data.low}-${data.data.high}`,
        type: data.data.type,
        date: data.data.date,
        week: data.data.week
      };
    } else {
      error.value = true;
      TkMessage.error('获取天气信息失败,请检查网络或者关闭代理'); // 显示错误提示
    }
  } catch (err) {
    console.error('获取天气信息失败', err);
  } finally {
    loading.value = false; // 加载结束
  }
};

// 储存舔狗日记内容
const diaryContent = ref('');

// 获取舔狗日记的函数
const getDiary = async () => {
  try {
    const response = await fetch('https://api.vvhan.com/api/text/dog?type=json');
    const data = await response.json();

    if (data.success) {
      diaryContent.value = data.data.content; // 获取内容
    } else {
      console.error('获取舔狗日记失败:', data.message);
    }
  } catch (fetchError) {
    console.error('获取舔狗日记失败', fetchError);
  }
};

const init = async () => {
  await getWeatherInfo(); // 获取天气信息
  await getDiary(); // 获取舔狗信息
};

// 新增:控制显示选项
const isConfigOpen = ref(false);
const showFPS = ref(true);
const showWeather = ref(true);
const showDate = ref(true);
const showTemperature = ref(true);
const showWeek = ref(true);
// const showgetDiary = ref(true);

// 新增:FPS计算
const fps = ref(0);
let frameCount = 0;
let lastTime = 0;

const updateFPS = (time: DOMHighResTimeStamp) => {
  if (lastTime === 0) {
    lastTime = time;
    requestAnimationFrame(updateFPS);
    return;
  }

  const delta = time - lastTime;
  frameCount += 1;

  if (delta > 1000) {
    fps.value = Math.round((frameCount * 1000) / delta);
    frameCount = 0;
    lastTime = time;
  }

  requestAnimationFrame(updateFPS);
};

onMounted(async () => {
  await init();
  requestAnimationFrame(updateFPS);
});

onMounted(() => {
  getWeatherInfo();
});
</script>

<template>
  <!-- 修改:欢迎卡片,包含天气信息和新功能 -->
  <ElCard class="info-card animate__animated animate__fadeIn welcome-card mobile-card" shadow="hover">
    <div class="welcome-content">
      <!-- 新增:FPS显示 -->
      <div v-if="showFPS" class="fps-display">FPS: {{ fps }}</div>

      <!-- 新增:配置开关 -->
      <El-Switch v-model="isConfigOpen" class="config-switch" active-color="#13ce66" inactive-color="#ff4949"></El-Switch>

      <!-- 配置面板 -->
      <div v-if="isConfigOpen" class="config-panel">
        <ElCheckbox v-model="showFPS">显示 FPS</ElCheckbox>
        <ElCheckbox v-model="showWeather">显示天气</ElCheckbox>
        <ElCheckbox v-model="showDate">显示日期</ElCheckbox>
        <ElCheckbox v-model="showTemperature">显示温度</ElCheckbox>
        <ElCheckbox v-model="showWeek">显示星期</ElCheckbox>
        <!-- <ElCheckbox v-model="showgetDiary">显示舔狗</ElCheckbox> -->
      </div>

      <!-- 欢迎信息 -->
      <template v-else>
        <h2 v-if="!error && weatherData.city" class="greeting">
          欢迎来自
          <span class="highlight">{{ weatherData.city }}</span>
          的小伙伴!🎉🎉🎉
        </h2>
        <div class="info-container">
          <div v-if="showTemperature" class="info-item">
            <i class="el-icon-sunny"></i>
            <span v-if="!error && weatherData.city">
              今日温度:
              <span class="highlight">{{ weatherData.temperature }}</span>
            </span>
          </div>
          <div v-if="showWeather" class="info-item">
            <i class="el-icon-cloudy"></i>
            <span v-if="!error && weatherData.city">
              天气:
              <span class="highlight">{{ weatherData.type }}</span>
            </span>
          </div>
          <div v-if="showDate" class="info-item">
            <i class="el-icon-date"></i>
            <span v-if="!error && weatherData.city">
              日期:
              <span class="highlight">{{ weatherData.date }}</span>
            </span>
          </div>
          <div v-if="showWeek" class="info-item">
            <i class="el-icon-calendar"></i>
            <span v-if="!error && weatherData.city">
              星期:
              <span class="highlight">{{ weatherData.week }}</span>
            </span>
          </div>
          <!-- <div v-if="showgetDiary" class="info-item">
            <i class="el-icon-calendar"></i>
            <h1 class="vertical-title">舔狗日记:</h1>
            <p v-if="diaryContent" class="diary-content">{{ diaryContent }}</p>
            <p v-else class="diary-content">加载中...</p>
          </div> -->
        </div>
      </template>
    </div>
  </ElCard>
</template>

<style lang="scss" scoped>
.welcome-card {
  margin: 4px;
  padding: 1.5rem;
  border-radius: 12px;
  text-align: center;
  font-size: 1.1rem;
  transition: all 0.3s ease;
  background: var(--day-bg);
  color: var(--day-text);
  box-shadow: 0 4px 6px var(--day-shadow);
  transform: translateY(0);
  position: relative;
  border: 1px solid rgba(255, 255, 255, 0); /* 完全透明的边框 */

  &.night-mode {
    background: var(--night-bg);
    color: var(--night-text);
    box-shadow: 0 4px 6px var(--night-shadow);

    &:hover {
      box-shadow: 0 10px 20px var(--night-shadow);
    }

    .highlight {
      color: var(--vp-c-brand-1);
    }
  }

  .welcome-content {
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }

  .greeting {
    font-size: 1.5rem;
    margin: 0;
    font-weight: bold;
  }

  .highlight {
    color: var(--vp-c-brand-1);
  }

  .info-container {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 1rem;
  }

  .info-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;

    i {
      font-size: 1.2rem;
    }
  }

  .fps-display {
    font-size: 0.9rem;
    font-weight: bold;
  }

  .config-switch {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
  }

  .config-panel {
    display: flex;
    flex-wrap: wrap; /* 自动换行 */
    justify-content: center; /* 水平居中对齐 */
    align-items: center; /* 垂直居中对齐 */
  }

  .config-panel .el-checkbox {
    width: 15%; /* 每个元素占据 15% 宽度,PC保持1列 */
    margin: 5px; /* 元素间距 */
    display: flex;
    justify-content: center; /* 文字与复选框居中 */
    align-items: center;
  }

  @media (max-width: 768px) {
    .config-panel .el-checkbox {
      width: 40%; /* 如果屏幕更小,双列显示 */
    }
  }
}
</style>


这个里的服务有问题,没法给自己博客提供服务,该如何处理呢?有国内的替换方案没?

豆包输出

从你提供的代码来看,服务有问题可能是网络连接、服务器状态或 API 本身的问题等。你可以参考以下方法进行处理:

  • 检查网络连接:确保你的网络连接正常,可以尝试刷新页面,或者重启路由器、切换网络等操作,看是否能解决问题。
  • 检查服务器状态:如果是服务器端的问题,比如服务器过载、正在进行维护等,你可以联系网站的管理员或者技术支持,了解服务器的状态,等待服务器恢复正常。
  • 检查 API 是否可用:你代码中使用的https://api.vvhan.com/api/weatherhttps://api.vvhan.com/api/text/dog?type=json这两个 API 可能存在问题,你可以直接在浏览器中访问这些 API 地址,看是否能正常返回数据。如果 API 不可用,你可以尝试寻找其他类似的 API 来替换。

国内有不少可以提供天气信息和文本内容的 API,以下是一些博客平台和 API 的替换方案供你参考:

  • 博客平台
    • CSDN 博客:国内最大的 IT 技术社区和博客平台,SEO 极好,文章容易被搜索引擎收录,用户活跃,互动性强,功能全面,支持 Markdown、代码片段等,适合几乎所有技术人员。
    • 掘金:一个面向开发者的高质量技术社区,UI 设计现代,体验很好,社区推荐机制不错,优质内容容易获得曝光,尤其适合前端和移动端开发者。
    • 语雀:阿里巴巴旗下的一款面向个人和团队的文档与知识库工具,其 “个人知识库” 功能非常适合用作博客,编辑器功能强大且优雅,对 Markdown、表格、脑图等支持完美,结构清晰,支持文档目录。
  • 天气 API
    • 和风天气 API:提供了丰富的天气数据接口,包括实时天气、预报天气、空气质量等,可以满足不同的需求,文档详细,使用方便。
    • 心知天气 API:提供准确的天气数据和专业的气象服务,支持多种数据格式输出,有免费版和付费版可供选择。
  • 文本内容 API:可以考虑使用一些国内的随机文案生成 API,例如今日热榜 API,可以获取各类热点新闻、段子等内容,你可以根据自己的需求进行筛选和展示。

问题2-待亲测

提出问题

bash
如何在Vue中使用其他API获取天气信息?

豆包回答

在 Vue(含 Vue3 + TypeScript)中使用第三方天气 API 获取数据,核心步骤是 选择合适的国内 API 服务配置请求参数(如城市、API 密钥)通过 fetchaxios 发送请求处理响应数据并渲染到页面。以下以 和风天气 API(国内主流、免费且文档清晰)为例,提供完整实现方案,同时兼容其他国内 API 的接入逻辑。

📌一、核心准备:选择国内天气 API 并获取密钥

首先需要选择一个稳定的国内天气 API(避免跨域和境外 API 不稳定问题),这里推荐 和风天气 API,步骤如下:

  1. 注册并创建应用 访问 和风天气开发者平台,注册账号后进入「控制台」→「应用管理」→「创建应用」,填写应用名称(如 “个人博客天气”),选择免费版套餐(支持实时天气、3 天预报等基础功能,足够个人使用)。
  2. 获取 API 密钥(Key) 应用创建后,在「应用详情」中会生成 API Key(如 a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q),这是请求 API 的必填参数(用于身份验证)。
  3. 了解核心接口 免费版常用接口:
    • 实时天气:https://devapi.qweather.com/v7/weather/now(获取当前温度、天气类型、风力等)
    • 3 天预报:https://devapi.qweather.com/v7/weather/3d(获取未来 3 天天气趋势)
    • 城市搜索:https://geoapi.qweather.com/v2/city/lookup(根据城市名 / IP 获取城市 ID,天气接口需传入城市 ID)

📌二、完整实现:在 Vue3 + TS 中接入和风天气 API

以你的博客场景为例,实现「自动获取用户城市 + 显示实时天气」的功能,步骤如下:

步骤 1:安装依赖(可选,用 axios 或原生 fetch

  • 若使用 axios(推荐,支持拦截器、错误处理更便捷): 执行命令安装:

    bash
    npm install axios
    # 或 yarn add axios
  • 若用原生 fetch:无需安装依赖,直接使用浏览器原生 API。

步骤 2:封装 API 请求逻辑(高复用性)

在项目中创建 src/api/weather.ts 文件,封装天气相关的请求函数(分离业务逻辑,便于维护):

typescript
// src/api/weather.ts
import axios from 'axios';

// 1. 配置和风天气 API 基础信息
const WEATHER_API_KEY = '你的和风天气API Key'; // 替换为自己的 Key
const GEO_API_BASE = 'https://geoapi.qweather.com/v2/city'; // 城市搜索接口基础路径
const WEATHER_API_BASE = 'https://devapi.qweather.com/v7/weather'; // 天气接口基础路径

// 2. 类型定义(TS 类型安全)
// 城市信息类型
export interface CityInfo {
  id: string; // 城市ID(天气接口必需)
  name: string; // 城市名(如“北京”)
  adminArea: string; // 省份(如“北京”)
}

// 实时天气数据类型
export interface RealTimeWeather {
  temp: string; // 温度(如“25”)
  feelsLike: string; // 体感温度
  text: string; // 天气类型(如“晴”“多云”)
  windDir: string; // 风向(如“南风”)
  windSpeed: string; // 风速(如“3”,单位 m/s)
  humidity: string; // 湿度(如“50”,单位 %)
}

// 3. 根据城市名获取城市ID(天气接口需要城市ID而非名称)
export const getCityIdByName = async (cityName: string): Promise<CityInfo | null> => {
  try {
    const response = await axios.get(`${GEO_API_BASE}/lookup`, {
      params: {
        key: WEATHER_API_KEY,
        location: cityName, // 城市名(如“上海”“广州”)
      },
    });

    const { code, location } = response.data;
    if (code === '200' && location.length > 0) {
      // 返回第一个匹配的城市(避免重名城市,如“西安”和“西安区”)
      return {
        id: location[0].id,
        name: location[0].name,
        adminArea: location[0].adminArea,
      };
    }
    console.error('未找到城市信息');
    return null;
  } catch (error) {
    console.error('获取城市ID失败:', error);
    return null;
  }
};

// 4. 根据城市ID获取实时天气
export const getRealTimeWeather = async (cityId: string): Promise<RealTimeWeather | null> => {
  try {
    const response = await axios.get(`${WEATHER_API_BASE}/now`, {
      params: {
        key: WEATHER_API_KEY,
        location: cityId, // 传入城市ID
      },
    });

    const { code, now } = response.data;
    if (code === '200' && now) {
      // 格式化返回数据(只保留需要的字段)
      return {
        temp: now.temp,
        feelsLike: now.feelsLike,
        text: now.text,
        windDir: now.windDir,
        windSpeed: now.windSpeed,
        humidity: now.humidity,
      };
    }
    console.error('获取天气失败,接口返回错误:', code);
    return null;
  } catch (error) {
    console.error('获取实时天气失败:', error);
    return null;
  }
};

// 5. (可选)自动获取用户城市(通过IP定位,需和风天气“IP定位”权限)
export const getCityByIp = async (): Promise<CityInfo | null> => {
  try {
    const response = await axios.get(`${GEO_API_BASE}/lookup`, {
      params: {
        key: WEATHER_API_KEY,
        location: 'auto_ip', // 传入 auto_ip 自动获取IP对应的城市
      },
    });

    const { code, location } = response.data;
    if (code === '200' && location.length > 0) {
      return {
        id: location[0].id,
        name: location[0].name,
        adminArea: location[0].adminArea,
      };
    }
    console.error('IP定位失败');
    return null;
  } catch (error) {
    console.error('IP定位请求失败:', error);
    return null;
  }
};

步骤 3:在 Vue 组件中使用 API

在你的天气卡片组件中(如 WeatherCard.vue),调用上述封装的 API 函数,处理加载、错误状态,并渲染数据:

vue
<!-- src/components/WeatherCard.vue -->
<template>
  <ElCard class="info-card animate__animated animate__fadeIn welcome-card mobile-card" shadow="hover">
    <div class="welcome-content">
      <!-- 加载状态 -->
      <div v-if="loading" class="loading">加载天气中...</div>

      <!-- 错误状态 -->
      <div v-else-if="error" class="error">获取天气失败,请稍后重试</div>

      <!-- 天气数据渲染 -->
      <div v-else-if="cityInfo && weatherData" class="weather-container">
        <h2 class="greeting">
          欢迎来自 <span class="highlight">{{ cityInfo.name }}</span> 的小伙伴!🎉
        </h2>
        <div class="info-container">
          <div class="info-item">
            <i class="el-icon-sunny"></i>
            温度:<span class="highlight">{{ weatherData.temp }}°C</span>
            (体感 {{ weatherData.feelsLike }}°C)
          </div>
          <div class="info-item">
            <i class="el-icon-cloudy"></i>
            天气:<span class="highlight">{{ weatherData.text }}</span>
          </div>
          <div class="info-item">
            <i class="el-icon-wind"></i>
            风向:<span class="highlight">{{ weatherData.windDir }}</span>
            风速:{{ weatherData.windSpeed }}m/s
          </div>
          <div class="info-item">
            <i class="el-icon-water"></i>
            湿度:<span class="highlight">{{ weatherData.humidity }}%</span>
          </div>
        </div>
      </div>
    </div>
  </ElCard>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { 
  getCityByIp, 
  getRealTimeWeather, 
  CityInfo, 
  RealTimeWeather 
} from '@/api/weather'; // 引入封装的API

// 状态管理
const loading = ref(false); // 加载中
const error = ref(false); // 错误状态
const cityInfo = ref<CityInfo | null>(null); // 城市信息
const weatherData = ref<RealTimeWeather | null>(null); // 天气数据

// 初始化:获取城市 + 天气
const initWeather = async () => {
  loading.value = true;
  error.value = false;
  try {
    // 1. 自动通过IP获取用户城市
    const city = await getCityByIp();
    if (!city) throw new Error('无法获取城市信息');
    cityInfo.value = city;

    // 2. 根据城市ID获取实时天气
    const weather = await getRealTimeWeather(city.id);
    if (!weather) throw new Error('无法获取天气数据');
    weatherData.value = weather;
  } catch (err) {
    console.error('初始化天气失败:', err);
    error.value = true;
  } finally {
    loading.value = false;
  }
};

// 组件挂载时初始化
onMounted(() => {
  initWeather();
});
</script>

<style lang="scss" scoped>
/* 保留你原有的样式,可根据需要调整 */
.welcome-card {
  margin: 4px;
  padding: 1.5rem;
  border-radius: 12px;
  text-align: center;
  background: var(--day-bg);
  color: var(--day-text);
  box-shadow: 0 4px 6px var(--day-shadow);

  .loading, .error {
    font-size: 1.1rem;
    padding: 1rem 0;
    color: var(--vp-c-text-2);
  }

  .weather-container {
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }

  .greeting {
    font-size: 1.5rem;
    margin: 0;
    font-weight: bold;
  }

  .highlight {
    color: var(--vp-c-brand-1);
  }

  .info-container {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 1.5rem;
    margin-top: 1rem;
  }

  .info-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 1.1rem;

    i {
      font-size: 1.2rem;
      color: var(--vp-c-brand-1);
    }
  }
}
</style>

三、关键问题处理(避坑指南)

  1. 跨域问题(CORS)

国内正规 API 服务商(如和风天气)已配置 CORS 跨域支持,前端直接请求即可,无需额外处理。若遇到跨域错误:

  • 检查 API 地址是否正确(和风天气用 devapi.qweather.com,非 api.qweather.com);
  • 确认 API Key 已正确配置(无 Key 可能返回 401 错误,间接导致跨域提示)。
  1. API 密钥安全
  • 若博客是 静态部署(如 VitePress、GitHub Pages),API Key 会暴露在前端代码中,存在被恶意调用的风险。解决方案:
    1. 限制 Key 的调用频率(在和风天气控制台设置 “日调用上限”,免费版默认 1000 次 / 天);
    2. 搭建一个简单的 后端代理(如用 Node.js/Express 转发请求,前端只请求自己的后端,不直接暴露 Key)。
  1. 城市重名问题

例如 “西安”(陕西省会)和 “西安区”(牡丹江市),通过 getCityByIp 自动定位可避免,若允许用户手动输入城市,需让用户选择具体城市(如下拉列表展示匹配的所有城市)。

📌四、其他国内天气 API 替换方案

若不想使用和风天气,可替换为以下国内 API,接入逻辑与上述一致(只需修改 weather.ts 中的接口地址和参数):

API 服务商免费额度核心接口特点官网地址
心知天气1000 次 / 天(基础版)支持全球天气、空气质量、生活指数https://www.seniverse.com/
高德开放平台10 万次 / 天(天气查询)结合高德地图,城市定位更精准https://lbs.amap.com/api/webservice/guide/api/weatherinfo
百度地图开放平台3000 次 / 天(普通天气)支持实时 / 预报天气、灾害预警https://lbsyun.baidu.com/index.php?title=open/weather

📌总结

在 Vue 中使用第三方天气 API 的核心是 “选择合规的国内 API → 封装请求逻辑 → 处理状态与渲染”。通过和风天气的示例,你可以快速实现博客的天气功能,同时根据实际需求调整城市获取方式(自动定位 / 手动输入)和数据展示内容(温度、风向、湿度等)。若需优化安全性,可增加后端代理层隐藏 API Key。

最近更新