Vue开发三年,我才发现依赖注入的TypeScript正确打开方式

作者:良山有风来日期:2025/12/2

你是不是也遇到过这样的场景?

在Vue项目里,为了跨组件传递数据,你用provideinject写了一套祖孙通信逻辑。代码跑起来没问题,但TypeScript编辑器总给你画红线,要么是“类型any警告”,要么就是“属性不存在”的错误提示。

你看着一片飘红的代码区,心里想着:“功能能用就行,类型标注太麻烦了。”于是,你默默地加上了// @ts-ignore,或者干脆把注入的值断言成any。项目在跑,但心里总觉得不踏实,像是在代码里埋下了一个个“类型地雷”。

别担心,这几乎是每个Vue + TypeScript开发者都会经历的阶段。今天这篇文章,就是来帮你彻底拆掉这些地雷的。

我会带你从最基础的any警告开始,一步步升级到类型安全、重构友好的最佳实践。读完这篇文章,你不仅能解决眼下的类型报错,更能建立一套完整的、类型安全的Vue依赖注入体系。无论你是维护大型中后台系统,还是开发独立的组件库,这套方法都能让你的代码更可靠、协作更顺畅。

为什么你的Provide/Inject总在报类型错误?

让我们先看一个非常典型的“反面教材”。相信不少朋友都写过,或者见过下面这样的代码:

1// 祖辈组件 - Grandparent.vue
2<script setup lang="ts">
3import { provide } from 'vue'
4
5// 提供一些配置和方法
6const appConfig = {
7  theme: 'dark',
8  apiBaseUrl: 'https://api.example.com'
9}
10
11const updateTheme = (newTheme: string) => {
12  console.log(`切换主题到:${newTheme}`)
13}
14
15// 简单粗暴的provide
16provide('appConfig', appConfig)
17provide('updateTheme', updateTheme)
18</script>
19

然后在子孙组件里这样注入:

1// 子孙组件 - Child.vue  
2<script setup lang="ts">
3import { inject } from 'vue'
4
5// 问题来了:类型是什么?编辑器不知道!
6const config = inject('appConfig')
7const updateFn = inject('updateTheme')
8
9// 当你尝试使用的时候
10const switchTheme = () => {
11  // 这里TypeScript会抱怨:updateFn可能是undefined
12  // 而且config也是any类型,没有任何类型提示
13  updateFn('light')  //  对象可能为"undefined"
14  console.log(config.apiBaseUrl) //  config是any,但能运行
15}
16</script>
17

看出来问题在哪了吗?

  1. 字符串键名容易写错'appConfig''appconfig'大小写不同,但TypeScript不会帮你检查这个拼写错误
  2. 注入值的类型完全丢失inject返回的类型默认是any或者unknown,你辛辛苦苦定义的类型信息在这里断掉了
  3. 缺乏安全性:如果上游没有提供对应的值,inject会返回undefined,但TypeScript无法确定这种情况

这就是为什么我们需要给Provide/Inject加上“类型安全带”。

从基础到进阶:四种类型标注方案

方案一:使用泛型参数(基础版)

这是最直接的方式,直接在inject调用时指定期望的类型。

1// 子孙组件
2<script setup lang="ts">
3import { inject } from 'vue'
4
5// 使用泛型告诉TypeScript:我期望得到这个类型
6const config = inject<{ theme: string; apiBaseUrl: string }>('appConfig')
7const updateFn = inject<(theme: string) => void>('updateTheme')
8
9// 现在有类型提示了!
10const switchTheme = () => {
11  if (config && updateFn) {
12    updateFn('light')  //  正确识别为函数
13    console.log(config.apiBaseUrl) //  知道apiBaseUrl是string
14  }
15}
16</script>
17

这种方法像是给TypeScript递了一张“期望清单”:“我希望拿到一个长这样的对象”。但缺点也很明显:

  • 类型定义是重复的(祖辈组件定义一次,每个注入的子孙组件都要写一次)
  • 键名还是字符串,容易拼写错误
  • 每次都要手动做空值检查

方案二:定义统一的注入键(进阶版)

我们可以定义专门的常量来管理所有的注入键,就像管理路由名称一样。

1// 首先,在一个单独的文件里定义所有注入键
2// src/constants/injection-keys.ts
3export const InjectionKeys = {
4  APP_CONFIG: Symbol('app-config'),        // 使用Symbol确保唯一性
5  UPDATE_THEME: Symbol('update-theme'),
6  USER_INFO: Symbol('user-info')
7} as const  // as const 让TypeScript知道这是字面量类型
8

然后在祖辈组件中使用:

1// Grandparent.vue
2<script setup lang="ts">
3import { provide } from 'vue'
4import { InjectionKeys } from '@/constants/injection-keys'
5
6interface AppConfig {
7  theme: 'light' | 'dark'
8  apiBaseUrl: string
9}
10
11const appConfig: AppConfig = {
12  theme: 'dark',
13  apiBaseUrl: 'https://api.example.com'
14}
15
16const updateTheme = (newTheme: AppConfig['theme']) => {
17  console.log(`切换主题到:${newTheme}`)
18}
19
20// 使用Symbol作为键
21provide(InjectionKeys.APP_CONFIG, appConfig)
22provide(InjectionKeys.UPDATE_THEME, updateTheme)
23</script>
24

在子孙组件中注入:

1// Child.vue
2<script setup lang="ts">
3import { inject } from 'vue'
4import { InjectionKeys } from '@/constants/injection-keys'
5
6// 类型安全地注入
7const config = inject(InjectionKeys.APP_CONFIG)
8const updateFn = inject(InjectionKeys.UPDATE_THEME)
9
10// TypeScript现在知道config的类型是AppConfig | undefined
11const switchTheme = () => {
12  if (config && updateFn) {
13    updateFn('light')  //  正确:'light'在主题范围内
14    // updateFn('blue') //  错误:'blue'不是有效主题
15  }
16}
17</script>
18

这个方法解决了键名拼写错误的问题,但类型定义仍然分散在各处。而且,如果你修改了AppConfig接口,需要在多个地方更新类型引用。

方案三:类型安全的注入工具函数(专业版)

这是我在大型项目中推荐的做法。我们创建一组工具函数,让Provide/Inject变得像调用API一样类型安全。

1// src/utils/injection-utils.ts
2import { InjectionKey, provide, inject } from 'vue'
3
4// 定义一个创建注入键的工具函数
5export function createInjectionKey<T>(key: string): InjectionKey<T> {
6  return Symbol(key) as InjectionKey<T>
7}
8
9// 再定义一个类型安全的provide函数
10export function safeProvide<T>(key: InjectionKey<T>, value: T) {
11  provide(key, value)
12}
13
14// 以及类型安全的inject函数
15export function safeInject<T>(key: InjectionKey<T>): T
16export function safeInject<T>(key: InjectionKey<T>, defaultValue: T): T
17export function safeInject<T>(key: InjectionKey<T>, defaultValue?: T): T {
18  const injected = inject(key, defaultValue)
19  
20  if (injected === undefined) {
21    throw new Error([`注入键 ${key.toString()} 没有被提供`](function toString() { [native code] }))
22  }
23  
24  return injected
25}
26

如何使用这套工具?

1// 首先,在一个集中位置定义所有注入类型和键
2// src/types/injection.types.ts
3import { createInjectionKey } from '@/utils/injection-utils'
4
5export interface AppConfig {
6  theme: 'light' | 'dark'
7  apiBaseUrl: string
8}
9
10export interface UserInfo {
11  id: number
12  name: string
13  avatar: string
14}
15
16// 创建类型安全的注入键
17export const APP_CONFIG_KEY = createInjectionKey<AppConfig>('app-config')
18export const USER_INFO_KEY = createInjectionKey<UserInfo>('user-info')
19export const UPDATE_THEME_KEY = createInjectionKey<(theme: AppConfig['theme']) => void>('update-theme')
20

在祖辈组件中提供值:

1// Grandparent.vue
2<script setup lang="ts">
3import { safeProvide } from '@/utils/injection-utils'
4import { APP_CONFIG_KEY, USER_INFO_KEY, UPDATE_THEME_KEY, type AppConfig } from '@/types/injection.types'
5
6const appConfig: AppConfig = {
7  theme: 'dark',
8  apiBaseUrl: 'https://api.example.com'
9}
10
11const userInfo = {
12  id: 1,
13  name: '张三',
14  avatar: 'https://example.com/avatar.jpg'
15}
16
17const updateTheme = (newTheme: AppConfig['theme']) => {
18  console.log(`切换主题到:${newTheme}`)
19}
20
21// 现在provide是类型安全的
22safeProvide(APP_CONFIG_KEY, appConfig)
23safeProvide(USER_INFO_KEY, userInfo)  //  自动检查userInfo是否符合UserInfo接口
24safeProvide(UPDATE_THEME_KEY, updateTheme)
25</script>
26

在子孙组件中注入:

1// Child.vue
2<script setup lang="ts">
3import { safeInject } from '@/utils/injection-utils'
4import { APP_CONFIG_KEY, UPDATE_THEME_KEY } from '@/types/injection.types'
5
6// 看!这里没有泛型参数,但类型完全正确
7const config = safeInject(APP_CONFIG_KEY)
8const updateFn = safeInject(UPDATE_THEME_KEY)
9
10// 直接使用,不需要空值检查
11const switchTheme = () => {
12  updateFn('light')  //  完全类型安全,且不会undefined
13  console.log(`当前API地址:${config.apiBaseUrl}`)
14}
15</script>
16

这种方案的优点是:

  1. 类型推导自动完成:不需要手动写泛型
  2. 编译时检查:如果你提供的值类型不对,TypeScript会在safeProvide那行就报错
  3. 运行时安全:如果注入键没有被提供,会抛出清晰的错误信息
  4. 重构友好:修改接口定义时,所有使用的地方都会自动更新

方案四:组合式API风格(现代最佳实践)

Vue 3的组合式API让我们的代码可以更好地组织和复用。对于依赖注入,我们可以创建专门的useXxx函数。

1// src/composables/useAppConfig.ts
2import { safeProvide, safeInject } from '@/utils/injection-utils'
3import { APP_CONFIG_KEY, UPDATE_THEME_KEY, type AppConfig } from '@/types/injection.types'
4
5// 提供者逻辑封装
6export function useProvideAppConfig(config: AppConfig, updateThemeFn: (theme: AppConfig['theme']) => void) {
7  safeProvide(APP_CONFIG_KEY, config)
8  safeProvide(UPDATE_THEME_KEY, updateThemeFn)
9  
10  // 返回一些可能需要的方法
11  return {
12    // 这里可以添加一些基于config的衍生逻辑
13    getThemeColor() {
14      return config.theme === 'dark' ? '#1a1a1a' : '#ffffff'
15    }
16  }
17}
18
19// 消费者逻辑封装
20export function useAppConfig() {
21  const config = safeInject(APP_CONFIG_KEY)
22  const updateTheme = safeInject(UPDATE_THEME_KEY)
23  
24  // 计算属性:自动响应式
25  const isDarkTheme = computed(() => config.theme === 'dark')
26  
27  // 方法:封装业务逻辑
28  const toggleTheme = () => {
29    const newTheme = config.theme === 'dark' ? 'light' : 'dark'
30    updateTheme(newTheme)
31  }
32  
33  return {
34    config,
35    updateTheme,
36    isDarkTheme,
37    toggleTheme
38  }
39}
40

在祖辈组件中使用:

1// Grandparent.vue
2<script setup lang="ts">
3import { useProvideAppConfig } from '@/composables/useAppConfig'
4
5const appConfig = {
6  theme: 'dark' as const,
7  apiBaseUrl: 'https://api.example.com'
8}
9
10const updateTheme = (newTheme: 'light' | 'dark') => {
11  console.log(`切换主题到:${newTheme}`)
12}
13
14// 一行代码完成所有provide
15const { getThemeColor } = useProvideAppConfig(appConfig, updateTheme)
16</script>
17

在子孙组件中使用:

1// Child.vue
2<script setup lang="ts">
3import { useAppConfig } from '@/composables/useAppConfig'
4
5// 像使用Vue内置的useRoute、useRouter一样
6const { config, isDarkTheme, toggleTheme } = useAppConfig()
7
8// 直接使用,所有类型都已正确推断
9const handleClick = () => {
10  toggleTheme()
11  console.log(`当前主题:${config.theme}`)
12}
13</script>
14

这种方式的强大之处在于:

  1. 逻辑高度复用:注入逻辑被封装起来,可以在多个组件中复用
  2. 开箱即用:使用者不需要关心注入的实现细节
  3. 类型完美推断:所有返回的值都有正确的类型
  4. 易于测试:可以单独测试useAppConfig的逻辑

实战:在组件库中应用类型安全注入

假设你正在开发一个UI组件库,需要提供主题配置、国际化、尺寸配置等全局设置。依赖注入是完美的解决方案。

1// 组件库的核心注入类型定义
2// ui-library/src/injection/types.ts
3export interface Theme {
4  primaryColor: string
5  backgroundColor: string
6  textColor: string
7  borderRadius: string
8}
9
10export interface Locale {
11  language: string
12  messages: Record<string, string>
13}
14
15export interface Size {
16  small: string
17  medium: string  
18  large: string
19}
20
21export interface LibraryConfig {
22  theme: Theme
23  locale: Locale
24  size: Size
25  zIndex: {
26    modal: number
27    popover: number
28    tooltip: number
29  }
30}
31
32// 创建注入键
33export const LIBRARY_CONFIG_KEY = createInjectionKey<LibraryConfig>('library-config')
34
35// 组件库的provide函数
36export function provideLibraryConfig(config: Partial<LibraryConfig>) {
37  const defaultConfig: LibraryConfig = {
38    theme: {
39      primaryColor: '#1890ff',
40      backgroundColor: '#ffffff',
41      textColor: '#333333',
42      borderRadius: '4px'
43    },
44    locale: {
45      language: 'zh-CN',
46      messages: {}
47    },
48    size: {
49      small: '24px',
50      medium: '32px',
51      large: '40px'
52    },
53    zIndex: {
54      modal: 1000,
55      popover: 500,
56      tooltip: 300
57    }
58  }
59  
60  const mergedConfig = { ...defaultConfig, ...config }
61  safeProvide(LIBRARY_CONFIG_KEY, mergedConfig)
62  
63  return mergedConfig
64}
65
66// 组件库的inject函数  
67export function useLibraryConfig() {
68  const config = safeInject(LIBRARY_CONFIG_KEY)
69  
70  return {
71    config,
72    // 一些便捷的getter
73    theme: computed(() => config.theme),
74    size: computed(() => config.size),
75    locale: computed(() => config.locale),
76    
77    // 主题相关的方法
78    setPrimaryColor(color: string) {
79      // 这里可以实现主题切换逻辑
80      config.theme.primaryColor = color
81    }
82  }
83}
84

在应用中使用你的组件库:

1// App.vue - 应用入口
2<script setup lang="ts">
3import { provideLibraryConfig } from 'your-ui-library'
4
5// 配置你的组件库
6provideLibraryConfig({
7  theme: {
8    primaryColor: '#ff6b6b',  // 自定义主题色
9    borderRadius: '8px'       // 更大的圆角
10  },
11  locale: {
12    language: 'en-US',
13    messages: {
14      'button.confirm': 'Confirm',
15      'button.cancel': 'Cancel'
16    }
17  }
18})
19</script>
20

在组件库的按钮组件中使用:

1// ui-library/src/components/Button/Button.vue
2<script setup lang="ts">
3import { useLibraryConfig } from '../../injection'
4
5const { theme, size } = useLibraryConfig()
6
7// 使用注入的配置
8const buttonStyle = computed(() => ({
9  backgroundColor: theme.value.primaryColor,
10  borderRadius: theme.value.borderRadius,
11  height: size.value.medium
12}))
13</script>
14
15<template>
16  <button :style="buttonStyle" class="library-button">
17    <slot></slot>
18  </button>
19</template>
20

这样,你的组件库就拥有了完全类型安全的配置系统。使用者可以享受完整的TypeScript支持,包括智能提示、类型检查和自动补全。

避坑指南:常见问题与解决方案

在实践过程中,你可能会遇到一些特殊情况。这里我总结了几种常见问题的解法。

问题一:注入值可能是异步获取的

有时候,我们需要注入的值是通过API异步获取的。这时候直接注入Promise不是一个好主意,因为每个注入的组件都需要处理Promise。

更好的做法是使用响应式状态:

1// 祖辈组件
2<script setup lang="ts">
3import { ref, provide } from 'vue'
4import { USER_INFO_KEY } from '@/types/injection.types'
5
6// 使用ref来管理异步状态
7const userInfo = ref<{ id: number; name: string } | null>(null)
8
9// 异步获取数据
10fetchUserInfo().then(data => {
11  userInfo.value = data
12})
13
14// 直接注入ref,子孙组件可以响应式地访问
15provide(USER_INFO_KEY, userInfo)
16</script>
17
18// 子孙组件
19<script setup lang="ts">
20import { inject } from 'vue'
21import { USER_INFO_KEY } from '@/types/injection.types'
22
23const userInfoRef = inject(USER_INFO_KEY)
24
25// 使用计算属性来安全访问
26const userName = computed(() => userInfoRef?.value?.name ?? '加载中...')
27</script>
28

问题二:需要注入多个同类型的值

如果需要在同一个应用中注入多个同类型的对象(比如多个数据源),可以使用工厂函数模式:

1// 创建带标识符的注入键
2export function createDataSourceKey(id: string) {
3  return createInjectionKey<DataSource>([`data-source-${id}`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.data.md))
4}
5
6// 在祖辈组件中
7provide(createDataSourceKey('user'), userDataSource)
8provide(createDataSourceKey('product'), productDataSource)
9
10// 在子孙组件中
11const userSource = safeInject(createDataSourceKey('user'))
12const productSource = safeInject(createDataSourceKey('product'))
13

问题三:类型循环依赖问题

在大型项目中,可能会遇到类型之间的循环依赖。这时可以使用TypeScript的interface前向声明:

1// types/moduleA.ts
2import type { ModuleB } from './moduleB'
3
4export interface ModuleA {
5  name: string
6  b: ModuleB  // 引用ModuleB类型
7}
8
9// types/moduleB.ts  
10import type { ModuleA } from './moduleA'
11
12export interface ModuleB {
13  id: number
14  a?: ModuleA  // 可选,避免强制循环
15}
16

或者在注入键中使用泛型:

1export function createModuleKey<T>() {
2  return createInjectionKey<T>('module')
3}
4
5// 使用时各自指定具体类型
6provide(createModuleKey<ModuleA>(), moduleAInstance)
7

结语:拥抱类型安全的Vue开发

回顾我们今天的旅程,我们从最开始的any类型警告,一步步升级到了类型安全、工程化的依赖注入方案。

让我为你总结一下关键要点:

  1. 永远不要忽略类型:那些// @ts-ignore注释就像是代码中的定时炸弹,总有一天会爆炸
  2. 选择合适的方案
    • 小项目:方案一或方案二就足够
    • 中大型项目:强烈推荐方案三或方案四
    • 组件库开发:方案四的组合式API模式是最佳选择
  3. 建立代码规范:在团队中统一依赖注入的写法,会让协作顺畅很多
  4. 利用工具函数:花点时间封装safeProvidesafeInject这样的工具函数,长期来看会节省大量时间

Vue开发三年,我才发现依赖注入的TypeScript正确打开方式》 是转载文章,点击查看原文


相关推荐


前端面试第 34 题:事件循环(Event Loop)—— 必考高频题
前端一课2025/11/29

下面是前端面试第 39 题:事件循环(Event Loop)—— 必考高频题 我会给你:最清晰版本 + 面试官喜欢的回答结构 + 对比 Node 与浏览器差异 ✅ 第 39 题:什么是事件循环(Event Loop)?宏任务/微任务的执行顺序? ⭐ 标准答法(面试官最爱) “事件循环(Event Loop)是一套用于协调同步任务与异步任务执行顺序的机制。 它决定代码、定时器、Promise、UI 渲染等在浏览器或 Node.js 中的执行顺序。” 🧩 事件循环的核心概念 1️⃣ JS 是单


让Trae CN SOLO自主发挥,看看能做出一个什么样的项目
银空飞羽2025/11/26

写在前面 很早之前就在公司内部用过这种全栈自动化开发的大模型工具,然后当时在用的Trae CN编辑器也一直说要推出该能力,早早的就加到了候补白名单中,终于在今天收到短信,正好借着运营小姐姐告诉我的平台活动,于是来试用一下。 界面展示 SOLO模式本质上就是开发者啥都不用干,给大模型一个指令,然后大模型自主完成从项目设计、代码开发、自主运行、到自动debug最终上线的一整套全栈开发流程。 你可以提前给它设定好技术栈,或者给它详细的产品设计,让它按照你的思路搞,最后由你来验收。 打开Trae CN


6个适合做 PoC 的开源无代码/低代码工具推荐
NocoBase2025/11/24

原文链接:www.nocobase.com/cn/blog/6-o… 如果在几年前你问一位产品经理或技术负责人:“最快完成一个 PoC 的方式是什么?”,大多数人都会给出类似的答案——选择一个合适的低代码或无代码平台,把业务流程、界面和基础逻辑快速搭建起来,使想法尽快进入可运行的状态。 但在过去两年里,AI 的快速发展也让这种判断开始发生变化。如今,模型能够根据自然语言生成前端代码、组件结构,甚至能根据草图生成完整页面。界面的生成速度显著提升,一部分前端工作已经可以由 AI 自动完成。近期推出的


Redis(140)Redis的Cluster的故障转移是如何实现的?
Victor3562025/11/23

Redis Cluster 的故障转移机制确保在主节点(Master)出现故障时,集群中的从节点(Slave)能够自动接管主节点的角色,从而保证高可用性。故障转移的实现涉及节点状态监控、故障检测、选举新主节点及更新集群状态等步骤。下面我们详细解析这些步骤,并结合代码示例进行深入探讨。 1. 节点状态监控和故障检测 每个 Redis 集群节点都会定期向其他节点发送 PING 消息,并期望收到 PONG 回复。如果在一定时间内未收到回复,该节点将认为目标节点可能失效。 代码示例 /* Cluster


基于51单片机的PD协议移动电源控制程序
t198751282025/11/21

含PD协议通信、电池管理、充电控制和状态显示等功能。 #include <reg52.h> #include <intrins.h> // 硬件引脚定义 sbit USB_CC1 = P1^0; // Type-C CC1检测引脚 sbit USB_CC2 = P1^1; // Type-C CC2检测引脚 sbit USB_DM = P1^2; // USB D- 引脚 sbit USB_DP = P1^3; // USB D+ 引脚 sbit L


嵌入式C++安全编码
普通网友2025/11/19

1、非修改序列算法 这些算法不会改变它们所操作的容器中的元素。 1.1 find 和 find_if find(begin, end, value):查找第一个等于 value 的元素,返回迭代器(未找到返回 end)。find_if(begin, end, predicate):查找第一个满足谓词的元素。find_end(begin, end, sub_begin, sub_end):查找子序列最后一次出现的位置。 vector<int> nums = {1, 3, 5, 7, 9};


苹果应用商店上架全流程 从证书体系到 IPA 上传的跨平台方法
aiopencode2025/11/18

将应用成功发布到苹果应用商店(App Store)往往是移动开发流程中最具挑战的一环。 相比 Android 的自由生态,苹果 App Store 在审核机制、签名系统、隐私要求等方面都有严格规范。 很多团队第一次上架都会遇到证书混乱、IPA 上传失败、审核被拒等问题。 好消息是——如今的工具生态已经成熟,无论你使用的是 macOS、Windows 或 Linux,都可以完成 App Store 上架。 本文将从实战开发者角度,完整梳理 “苹果应用商店上架” 的必要步骤、工具选择与跨平台处理方式


下载安装pycharm 并通过pycahrm来驱动Anaconda来运行Python程序
BugMaker01142025/11/17

目录 下载安装创建新项目 下载 点击跳转官方下载地址 点击下载 建议下载最新版再往前几个版本 选择需要的版本 安装 双击安装包 选择目标安装路径 选择需要的选项 然后选择下一步并安装 如果需要破解 完成安装后先关掉pycharm 再点击 然后再打开pycharm 创建新项目 选择创建新项目 选择现有环境 因为已经安装过了Anaconda 然后选择create


Python 的内置函数 object
IMPYLH2025/11/16

Python 内建函数列表 > Python 的内置函数 object Python 的内置函数 object 是 Python 中最基础的类,它是所有类的基类。在 Python 中,所有的类都直接或间接地继承自 object 类。object 类提供了一些默认的方法和属性,这些方法和属性可以被所有 Python 对象使用。 基本特性 继承关系:所有 Python 类默认都继承自 object。例如,定义一个空类时,实际上它已经隐式地继承了 object 类。 class MyClass


vue2中实现天气预报
王阔阔2025/11/14

vue2中实现天气预报功能 实现效果图静态页完整代码echarts组件代码最终实现页面渲染使用到的函数年-月-日字符串转为 昨天、今天、明天、周几 实现效果图 静态页完整代码 <template> <div class="weather-container"> <div class="weather-top"> <!-- 市区选择和更新时间 --> <p class="city-select padding-l-r-10">

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2025 聚合阅读