Vue2+TypeScript+ElementUI 天气预报实战:装饰器通信与接口封装

作者:终端旅客时间:2026-10-26 08:43:18浏览:96

本文介绍基于 Vue2、TypeScript 和 ElementUI 的天气预报 Demo 实现。重点解析组件间通过 @Emit 和 @Prop 装饰器进行数据传递,利用 @Watch 监听状态变化,并封装 Axios 网络请求及定义 TypeScript 接口以处理返回数据。

本入门练手项目采用 Vue2 结合 TypeScript 与 SCSS 技术栈,构建了一个简单的天气预报 Demo。整体实现思路清晰,页面仅包含搜索框、热门城市列表及天气预报展示三个部分,UI 组件库选用 ElementUI。

搜索框 searchBox.vue

<template>
  <div id="search">
    <el-input
      placeholder="请输入内容"
      suffix-icon="el-icon-search"
      v-model="city"
      @change="enter"
      @input="edit"
    >
    </el-input>
  </div>
</template>

<script lang="ts">
import { Vue, Component, Emit } from "vue-property-decorator";

@Component({
  components: {},
})
export default class searchBox extends Vue {
  city = "";

  @Emit("sendCity")
  enter(value: string): string {
    //在输入框失去焦点或用户按下回车时触发(会导致一些bug!!!)
    console.log("按下回车,搜索地是:" + value);
    return value;
  }

  edit(value: string | number): void {
    console.log("正在输入中......" + value);
  }
}
</script>

在组件中若省略 @component 注解,会导致组件内部数据与方法失去响应式特性。初期开发时容易忽略此细节,导致点击 el-input 输入框无反应,误以为是逻辑或 ElementUI 本身的问题,查阅资料后确认是未添加 @component 所致。searchBox 子组件需将数据传递给 weather 组件,二者为兄弟关系,需借助父组件 Home 进行通信。在 TypeScript 环境下,子组件向父组件发送数据使用 @Emit 装饰器,父组件通过对应函数接收;而父组件向子组件传递数据则使用 @Prop 装饰器。

首页 Home.vue

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <searchBox @sendCity="sendCity" />
    <popularCity @popularCity="clickCity" />
    <weather :searchCity="city" :popularCity="city" />
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import searchBox from "@/components/searchBox.vue";
import popularCity from "@/components/popularCity.vue";
import weather from "@/components/weather.vue";

@Component({
  components: {
    searchBox,
    popularCity,
    weather,
  },
})
export default class Home extends Vue {
  city = "";

  sendCity(city: string): void {
    //搜索框组件向home组件传值
    this.city = city;
  }

  clickCity(city: string): void {
    //热门城市传值
    this.city = city;
  }
}
</script>

热门城市 popularCity.vue

<template>
  <div id="city">
    <div v-for="(item, index) in message" :key="index">
      <el-button class="box-city" type="text" @click="clickCity(item)">{{
        item
      }}</el-button>
    </div>
  </div>
</template>

<script lang="ts">
import { Vue, Component, Emit } from "vue-property-decorator";

@Component({
  components: {},
})
export default class searchBox extends Vue {
  message = ["北京", "上海", "深圳", "成都", "重庆", "武汉", "南京"];

  @Emit("popularCity")
  clickCity(city: string): string {
    console.log("点击热门城市:" + city);
    return city;
  }
}
</script>

<style lang="scss" scoped>
@import "../style/index.scss";
#city {
  width: 40%;
  @include box-row-flex(center);

  .box-city {
    width: 10%;
    font-style: italic;
    color: $text-color;
    font-size: $font-size;
  }
}
</style>

该部分主要涉及 SCSS 的初步尝试,例如使用了 @mixin 混合器进行样式复用。

天气 weather.vue

<template>
  <div id="weather">
    <div v-for="(item, index) in weatherArr" :key="index">
      <el-card class="box-card">
        <div slot="header" class="clearfix">
          <span>{{ city }}</span>
        </div>
        <div class="content">
          <div class="type">{{ item.type }}</div>
          <div class="temp">
            {{ item.low | temperatureFilter }} ~
            {{ item.high | temperatureFilter }}
          </div>
          <div class="time">{{ item.date }}</div>
        </div>
      </el-card>
    </div>
  </div>
</template>

<script lang="ts">
import weather from "../interface/IWeather";
import getWeather from "../utils/getWeather";
import { Vue, Component, Prop, Watch } from "vue-property-decorator";

@Component({
  components: {},
  filters: {
    //过滤器
    temperatureFilter: (value: string): string => {
      return value.substring(2);
    },
  },
})
export default class searchBox extends Vue {
  @Prop({
    type: String,
    default: "",
  })
  searchCity!: string;

  city = "西安";
  weatherArr: Array<weather> = [];

  @Watch("searchCity")
  async handleWatch(value: string): Promise<void> {
    console.log("搜索框或热门城市传入的地区是:" + value);

    const res = await getWeather(value);
    console.log(res);
    if (res.status == 1000) {
      this.city = value;
      this.weatherArr.length = 0; //清空当前数组存入的数据
      this.weatherArr.push(...res.weather);
    } else if (res.status == 1002) {
      this.$message({
        message: res.desc as string,
        type: "error",
      });
    }
  }

  async created(): Promise<void> {
    const res = await getWeather("西安");
    if (res.status == 1000) {
      this.weatherArr.push(...res.weather);
    } else if (res.status == 1002) {
      this.$message({
        message: res.desc as string,
        type: "error",
      });
    }

    console.log(res);
  }
}
</script>

此部分为整个 Demo 的核心逻辑,负责接收来自其他组件的数据,发起网络请求,并获取最终的天气信息。

关于数据传递,前文已提及父子组件通过 @Prop 通信。在此处,利用 @Watch 装饰器监听数据变化。当用户点击热门城市或搜索框按下回车键时,数据会传递至该组件,随后通过封装好的 axios 发送请求以获取天气数据。网络请求部分已进行独立封装处理。

同时,根据接口返回的数据结构定义 interface。为了展示数据并根据不同的返回码 status 提示相应消息,创建了 IWeather 接口以抽象天气数据,以及 IFiveWeather 接口来定义接口返回的形式(具体接口代码此处省略)。

getWeather.ts

import axios from "axios";

//获取某地的天气
async function getWeather(city: string): Promise<IFiveWeather> {
  const weatherArr: IFiveWeather = {
    status: 0,
    weather: [],
  };
  try {
    const res = await axios.get(
      `http://wthrcdn.etouch.cn/weather_mini?city=${city}`
    );

    const status: number = res.data.status;

    switch (status) {
      //输入城市错误的返回码
      case 1002:
        weatherArr.status = 1002;
        weatherArr.desc = res.data.desc;
        weatherArr.weather = [];
        break;
      //数据返回成功
      case 1000:
        weatherArr.status = 1000;
        weatherArr.weather.push(...res.data.data.forecast);
    }
  } catch (error) {
    console.log("天气接口出错啦:" + error);
  }

  return weatherArr;
}

export default getWeather;

至此,关于 TypeScript 在 Vue 中的入门案例代码 Demo 介绍完毕。如需了解更多相关 TypeScript 入门 Demo 内容,请搜索本站历史文章或继续浏览后续文章,感谢支持。

作者最新文章
相关文章
更多