Notification 本地通知:定时消息、点击路由跳转、桌面角标适配

本文基于 HarmonyOS NEXT(API 12+)Stage 模型,所有接口签名与字段说明均对照官方 API 参考核对。文中标注了 API 11 起废弃的写法,供老项目迁移参考。

  1. "定时"到底归谁管 :很多人第一反应是 NotificationRequest.deliveryTime,但它是只读的、系统自动生成的字段,你写进去也不会生效。
  2. 点击跳转WantAgent 有一批 API 11 废弃的字段,抄老博客会直接踩到;冷启动/热启动两条路径要分别处理。
  3. 桌面角标badgeNumber 字段是累加 语义,setBadgeNumber()直接设定语义,两者混用会出现"清了又冒出来"的经典 bug。

下面按这个顺序拆开讲。


一、先搞懂:Notification 没有"定时发送"能力

打开 NotificationRequest 的字段表,会看到 deliveryTime

字段 类型 说明
deliveryTime number 通知发送时间。系统自动生成,无需开发者配置。时间戳,单位 ms
showDeliveryTime boolean 是否显示分发时间。预留能力,暂未支持

也就是说,deliveryTime 是系统在通知落库时打的时间戳,用来记录/展示"这条通知什么时候发的",不是让你预约发送时间的入口。往里塞一个未来时间戳,不会让通知延迟出现。

另外要明确本地通知的边界:本地通知由你的应用进程发布,进程不存活就发不出来。想实现"应用退到后台甚至被杀掉之后,到点仍然弹出提醒",必须依赖系统代理------这正是**代理提醒(Agent-powered Reminder)**存在的理由。

所以定时消息的正确打开方式是:

css 复制代码
定时能力  →  reminderAgentManager(Background Tasks Kit,代理提醒)
展示能力  →  Notification Kit(代理提醒到点后由系统调用它发通知)

二、通知授权:一切的前提

没有用户授权,后面所有代码都是空转。发布前必须先申请。

ts 复制代码
import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 在 UIAbility 中调用
onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('pages/Index', (err) => {
    if (err.code) {
      return;
    }
    // ⚠️ 必须等 loadContent 成功后才能调用,否则拉起弹窗失败
    notificationManager.requestEnableNotification(this.context)
      .then(() => {
        hilog.info(0x0000, 'notify', '用户已授权');
      })
      .catch((e: BusinessError) => {
        // 1600004:用户已拒绝
        // 1600013:已有授权弹窗在展示中
        hilog.error(0x0000, 'notify', `授权失败 code=${e.code} msg=${e.message}`);
      });
  });
}

三个必须记住的行为:

  • requestEnableNotification() 只能在 loadContent 成功之后调用,否则弹窗拉不起来。
  • 用户拒绝后,这个接口不会再弹第二次 。二次引导要走 notificationManager.openNotificationSettingsWithResult()(API 13+)拉起通知管理页,让用户手动打开。
  • 每次都先查状态,不要无脑弹isNotificationEnabledSync()(API 12+,同步)或 isNotificationEnabled()(异步)返回 false 时,直接走引导逻辑。
ts 复制代码
import { notificationManager } from '@kit.NotificationKit';

if (!notificationManager.isNotificationEnabledSync()) {
  // 走设置页引导,而不是再调 requestEnableNotification
  await notificationManager.openNotificationSettingsWithResult(this.context);
}

三、发一条基础通知

3.1 先选渠道类型

渠道类型(SlotType)决定这条通知的提醒强度,它会直接影响横幅、提示音、锁屏展示

渠道类型 对应级别 适合场景
SOCIAL_COMMUNICATION 1 LEVEL_HIGH 社交通信、IM 消息
SERVICE_INFORMATION 2 LEVEL_HIGH 服务提醒、订单/物流/待办
CONTENT_INFORMATION 3 LEVEL_MIN 内容资讯(默认静默,无横幅无声音)
CUSTOMER_SERVICE 5 LEVEL_DEFAULT 客服消息,需用户主动发起
OTHER_TYPES 0xFFFF LEVEL_MIN 其他(默认值)

常见事故 :把定时提醒发在 CONTENT_INFORMATION 渠道上,结果到点只有状态栏一个小图标,既没横幅也没声音------因为该渠道对应 LEVEL_MIN。提醒类通知请用 SERVICE_INFORMATION

3.2 字段名注意废弃

ts 复制代码
// ❌ API 11 起废弃
content: { contentType: ... }
notificationSlotType: ...   // 对应的旧字段是 slotType,已废弃

// ✅ API 11+
content: { notificationContentType: ... }
notificationSlotType: notificationManager.SlotType.SERVICE_INFORMATION

3.3 完整发布代码

ts 复制代码
import { notificationManager } from '@kit.NotificationKit';

async function publishBasic(title: string, text: string): Promise<void> {
  const request: notificationManager.NotificationRequest = {
    id: 1001,                                     // 同 id 重复发布 = 更新该条通知
    notificationSlotType: notificationManager.SlotType.SERVICE_INFORMATION,
    content: {
      notificationContentType:
        notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
      normal: {
        title: title,        // 不可为空串,上限 1024 字节
        text: text,
        additionalText: ' '
      }
    },
    // 可选:点击后是否自动清除当前通知,默认 true
    tapDismissed: true,
    // 可选:到达该时间戳后自动清除通知
    // autoDeletedTime: Date.now() + 30_000
  };

  await notificationManager.publish(request);
}

几个容易忽略的点:

  • id 是覆盖键 。同一个 id 再发布一次,是"更新"而不是"新增"。
  • tapDismissed 只在通知携带 wantAgentactionButton 时生效 。默认 true,即点击后自动消失;如果你的业务希望点完仍留在通知中心(比如"标记已读"),要显式设 false
  • autoDeletedTime 才是真正的"定时消除"------它是通知的过期清除时间,跟"定时发送"是两回事,别混淆。

四、定时消息:代理提醒三件套

代理提醒提供三种提醒类型:

类型 枚举值 关键字段 场景
倒计时 REMINDER_TYPE_TIMER triggerTimeInSeconds 番茄钟、倒计时结束提醒
日历 REMINDER_TYPE_CALENDAR dateTimerepeatMonths/repeatDays/daysOfWeek 指定日期时间的一次性/周期性提醒
闹钟 REMINDER_TYPE_ALARM hourminutedaysOfWeek 每日固定时刻提醒(打卡、吃药)

4.1 前置准备

(1) 声明权限 :在 module.json5 中声明:

json5 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.PUBLISH_AGENT_REMINDER"
      }
    ]
  }
}

(2) 申请通知授权 :官方文档明确要求,publishReminder 需要在调用过 requestEnableNotification() 之后才能用(见第二节)。

(3) AGC 侧开通"代理提醒"开放能力 :这是上线前最容易漏的一步 。代理提醒属于受控开放能力,需要在 AppGallery Connect 申请开通并重新生成 Profile 签名,否则真机上 publishReminder 会失败。具体入口与材料要求以官方最新说明为准,建议在开发排期里就把这条挂上。

4.2 发布日历提醒

ts 复制代码
import { reminderAgentManager } from '@kit.BackgroundTasksKit';
import { notificationManager } from '@kit.NotificationKit';
import { BusinessError } from '@kit.BasicServicesKit';

async function publishCalendarReminder(
  target: Date,
  title: string,
  content: string
): Promise<number> {
  const calendar: reminderAgentManager.ReminderRequestCalendar = {
    reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_CALENDAR,
    dateTime: {
      year: target.getFullYear(),
      month: target.getMonth() + 1,        // ⚠️ 取值 [1,12],别直接用 getMonth()
      day: target.getDate(),
      hour: target.getHours(),
      minute: target.getMinutes(),
      second: 0
    },
    // 三方应用最多两个按钮
    actionButton: [
      {
        title: '关闭',
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE
      },
      {
        title: '稍后提醒',
        type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_SNOOZE
      }
    ],
    // 点击提醒后跳转的目标(简化对象,不是 WantAgent 实例)
    wantAgent: {
      pkgName: 'com.example.demo',
      abilityName: 'EntryAbility',
      parameters: { from: 'reminder', reminderId: 1001 }
    },
    ringDuration: 5,                                    // 单位 s,范围 [0, 1800]
    snoozeTimes: 3,                                     // 延时提醒次数
    timeInterval: 300,                                  // 延时间隔,单位 s,最少 30
    title: title,
    content: content,
    expiredContent: '提醒已过期',
    snoozeContent: '稍后再次提醒你',
    notificationId: 1001,          // ⚠️ 相同 id 的提醒会互相覆盖
    slotType: notificationManager.SlotType.SERVICE_INFORMATION,
    tapDismissed: true
  };

  try {
    const reminderId: number = await reminderAgentManager.publishReminder(calendar);
    // ⚠️ 一定要持久化这个 id,取消/更新时都要用
    return reminderId;
  } catch (e) {
    const err = e as BusinessError;
    if (err.code === 1700001) {
      // 通知未开启 ------ 引导用户去授权
    } else if (err.code === 1700002) {
      // 提醒数量超限
    }
    throw e;
  }
}

4.3 取消与查询

ts 复制代码
// 按 id 取消
await reminderAgentManager.cancelReminder(reminderId);

// 取消本应用所有提醒
await reminderAgentManager.cancelAllReminders();

// 查询所有仍然有效的提醒(用于页面回显 / 数据校准)
const validList: reminderAgentManager.ReminderRequest[] =
  await reminderAgentManager.getValidReminders();

getValidReminders() 非常有用:本地数据库和系统里实际存在的提醒很容易漂移(用户卸载重装、系统清理、异常中断),每次进入提醒列表页时用它做一次对账,比信任本地存储可靠得多。

4.4 约束与限制(务必先看)

  • 个数限制 :一个普通应用最多 30 个有效提醒 ,系统应用最多 10000 个,整个系统上限 12000 个。超限报 1700002
  • 有效期语义 :到点弹出后,如果用户没点"关闭"按钮,这条提醒仍然算有效/未过期 ;点了关闭才算过期。周期性提醒(如"每天提醒")无论是否点关闭,始终有效。这意味着非周期提醒用完后要主动 cancelReminder,否则会持续占用 30 个名额。
  • 跳转限制 :点击提醒跳转的必须是申请代理提醒的本应用,不能跳到别的 App。
  • 提醒表现受系统设置影响 :勿扰模式、关闭横幅、静音都会改变实际表现,publishReminder 成功只代表系统已受理,不代表一定准时高调弹出
  • 模拟器:API 20 起支持在模拟器上开发调试,之前的版本需要真机。

五、点击路由跳转

这里有个关键分叉:代理提醒和普通通知,用的不是同一个 wantAgent 类型

场景 wantAgent 类型 构造方式
普通通知 NotificationRequest wantAgent.WantAgent 实例 await wantAgent.getWantAgent(info)
代理提醒 ReminderRequest 普通对象 { pkgName, abilityName, parameters?, uri? } 直接字面量

抄代码时经常混用,导致点击无反应或者跳转目标不对。

5.1 构造 WantAgent(普通通知用)

ts 复制代码
import { wantAgent, Want } from '@kit.AbilityKit';

async function buildWantAgent(detailId: string): Promise<wantAgent.WantAgent> {
  const want: Want = {
    bundleName: 'com.example.demo',
    abilityName: 'EntryAbility',
    parameters: {
      from: 'notification',
      detailId: detailId
    }
  };

  const info: wantAgent.WantAgentInfo = {
    wants: [want],        // ⚠️ 预留能力,当前只取数组第一个元素
    actionType: wantAgent.OperationType.START_ABILITY,
    requestCode: 1001,
    actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
  };

  return await wantAgent.getWantAgent(info);
}

废弃字段对照(API 11 起)

旧写法(已废弃) 新写法(API 11+)
operationType actionType
wantAgentFlags actionFlags

另外,NOTIFICATION_CONTROLLER 之外还有一个高频需求------通知被用户移除时触发逻辑 ,对应 NotificationRequest.removalWantAgent。注意它有硬限制:当前只支持发布公共事件,不支持跳转 UIAbility (即 actionType 必须取 SEND_COMMON_EVENT,值 4)。

5.2 requestCodeactionFlags 的配合

这两个字段决定"重复发布同一 id 的通知时,旧的 WantAgent 会怎样":

actionFlags 行为
ONE_TIME_FLAG 0 只能触发一次,触发后自动取消
NO_BUILD_FLAG 1 不存在则不创建,直接返回 null
CANCEL_PRESENT_FLAG 2 创建新的之前,先取消已存在的
UPDATE_PRESENT_FLAG 3 用新 WantAgent 的额外数据替换已存在的
CONSTANT_FLAG 4 WantAgent 不可变

实务建议 :如果 requestCode 固定,而每次通知携带的 detailId 不同,必须带 UPDATE_PRESENT_FLAG ,否则系统复用的还是第一次那个 WantAgent,用户点第二条通知跳进的却是第一条的详情页------这是最高频的路由 bug 之一。更稳妥的做法是用业务唯一值参与 requestCode,避免复用。

5.3 接收端:冷启动与热启动要分别处理

在 Stage 模型下,点击通知拉起应用有两条路径:

  • 冷启动 (进程不在)→ 走 onCreate
  • 热启动 (进程还在,Ability 已存在)→ 走 onNewWant

只写 onCreate 是最常见的漏配,表现为"第一次点能跳,第二次点没反应"。

ts 复制代码
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.handleNotificationWant(want);
  }

  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.handleNotificationWant(want);
  }

  private handleNotificationWant(want: Want): void {
    const params = want.parameters;
    if (!params) {
      return;
    }
    const from = params['from'] as string;
    if (from !== 'notification' && from !== 'reminder') {
      return;
    }

    // ⚠️ 关键:不要在 Ability 里直接 router.pushUrl
    // 此时 UI 可能还没 ready,路由会失败
    // 正确做法是暂存"待跳转意图",由页面消费
    AppStorage.setOrCreate('pendingRoute', {
      from: from,
      detailId: (params['detailId'] as string) ?? '',
      reminderId: (params['reminderId'] as number) ?? 0
    });
  }
}

页面侧消费:

ts 复制代码
import { router } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  aboutToAppear(): void {
    const pending = AppStorage.get<Record<string, Object>>('pendingRoute');
    if (!pending) {
      return;
    }
    // 消费后立刻清掉,避免返回时重复跳转
    AppStorage.delete('pendingRoute');

    if (pending['from'] === 'notification') {
      AppStorage.setOrCreate('currentDetailId', pending['detailId']);
      router.pushUrl({ url: 'pages/DetailPage' });
    }
  }

  build() { /* ... */ }
}

两个补充要点

  • module.json5EntryAbility 建议配 "launchType": "singleton",避免每次点击通知都新起一个 Ability 实例。
  • want.parameters 里只放可序列化的基础类型 (string / number / boolean)。传对象、类实例很容易在跨进程后丢掉或变成 undefined。复杂数据请只传 id,落地后回查。

六、桌面角标适配

角标有两条路径,语义完全不同,这是最容易出 bug 的地方

6.1 两种设置方式的区别

方式 语义 说明
通知携带 badgeNumber 字段 累加 每发布一条按值累加,≤0 时忽略本次设定,>99 显示 99+
notificationManager.setBadgeNumber(n) 直接设定 桌面按 n 直接呈现,n ≤ 0 时清除角标

官方文档给的累加示例非常直观:

应用发布 3 条通知,badgeNumber 依次设置为 203,应用将依次展示为 225

注意第二个值是 0 ------ 0 被忽略,角标仍停在 2 。很多人以为 badgeNumber: 0 能清零,这是错的。

ts 复制代码
// 方式 A:随通知累加(适合"来一条消息 +1")
const request: notificationManager.NotificationRequest = {
  id: 1001,
  badgeNumber: 1,
  notificationSlotType: notificationManager.SlotType.SOCIAL_COMMUNICATION,
  content: { /* ... */ }
};
await notificationManager.publish(request);

// 方式 B:直接设定 / 清零(适合"进入应用后显示未读总数")
await notificationManager.setBadgeNumber(5);   // 桌面显示 5
await notificationManager.setBadgeNumber(0);   // 清除角标

6.2 "清了又冒出来"是怎么来的

典型的事故链是这样:

  1. 收消息时用 badgeNumber: 1 累加 → 角标涨到 5。
  2. 用户进入应用,调 setBadgeNumber(0) 清零。
  3. 但此时通知中心里还挂着 5 条未读通知。用户点了其中一条,或系统重新渲染通知列表,累加逻辑再次生效 → 角标又变回 5。

根因是混用两种机制:一种在"加",一种在"设",而通知中心的存量通知会持续触发"加"。

解决思路二选一,不要混用

  • 想用 setBadgeNumber 做精确控制 → 清角标的同时清理对应通知notificationManager.cancel(id) / cancelAll() / cancelGroup(groupName)),否则存量通知会把它顶回去。
  • 想用 badgeNumber 累加 → 就别再调 setBadgeNumber,靠"发布通知"和"取消通知"来驱动角标。

另外,setBadgeNumber异步接口,连续调用必须串行 (用 await 排队)。并发调用会让最终值不可预期。

6.3 角标不显示的排查清单

按顺序排查,基本能覆盖 95% 的情况:

  1. 通知授权是否拿到?没授权一切白搭。
  2. 系统"桌面角标"开关是否打开 ?路径大致在 设置 > 通知 > 应用 > 桌面角标。部分 ROM 需要同时打开"通知开关"和"桌面角标"两项。
  3. 通知渠道是否允许显示角标 ?渠道字段 badgeFlag(默认 true)控制是否显示角标,检查一下是不是被关掉了。
  4. 设备能力差异 :部分设备/平板只支持红点,不支持数字角标;Wearable 上部分接口直接返回 801(Capability not supported,API 18 起)。
  5. 接口是否静默失败setBadgeNumber 失败不会弹任何提示,必须自己接 catch 打日志

还有一条反直觉的行为,官方 FAQ 里专门提过:打开应用、点击通知、手动清理通知,都不会自动清除角标数字。想让角标归零,必须显式调用清除逻辑。所以"进入应用首页时清角标"这行代码,得你自己写。


七、封装:一个能直接用的工具类

把上面的逻辑收拢成一个 helper,业务侧只关心"我要提醒什么、点了跳哪里"。

ts 复制代码
import { notificationManager } from '@kit.NotificationKit';
import { reminderAgentManager } from '@kit.BackgroundTasksKit';
import { wantAgent, Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

const BUNDLE = 'com.example.demo';
const ABILITY = 'EntryAbility';

export class NotifyHelper {
  /** 检查并引导通知授权 */
  static async ensureEnabled(): Promise<boolean> {
    if (notificationManager.isNotificationEnabledSync()) {
      return true;
    }
    try {
      await notificationManager.requestEnableNotification(AppStorage.get('ctx'));
      return true;
    } catch (e) {
      return false;
    }
  }

  /** 发布一条可点击跳转的普通通知 */
  static async publish(opts: {
    id: number;
    title: string;
    text: string;
    route?: Record<string, Object>;
    badge?: number;
    slot?: notificationManager.SlotType;
  }): Promise<void> {
    let agent: wantAgent.WantAgent | undefined = undefined;

    if (opts.route) {
      const want: Want = {
        bundleName: BUNDLE,
        abilityName: ABILITY,
        parameters: opts.route
      };
      agent = await wantAgent.getWantAgent({
        wants: [want],
        actionType: wantAgent.OperationType.START_ABILITY,
        // 用 id 参与 requestCode,避免不同通知复用同一个 WantAgent
        requestCode: opts.id,
        actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
      });
    }

    const request: notificationManager.NotificationRequest = {
      id: opts.id,
      notificationSlotType:
        opts.slot ?? notificationManager.SlotType.SERVICE_INFORMATION,
      badgeNumber: opts.badge,
      wantAgent: agent,
      content: {
        notificationContentType:
          notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: { title: opts.title, text: opts.text, additionalText: ' ' }
      }
    };

    await notificationManager.publish(request);
  }

  /** 发布定时提醒,返回 reminderId(请务必持久化) */
  static async scheduleAt(
    target: Date,
    title: string,
    content: string,
    notificationId: number
  ): Promise<number> {
    const req: reminderAgentManager.ReminderRequestCalendar = {
      reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_CALENDAR,
      dateTime: {
        year: target.getFullYear(),
        month: target.getMonth() + 1,
        day: target.getDate(),
        hour: target.getHours(),
        minute: target.getMinutes()
      },
      wantAgent: {
        pkgName: BUNDLE,
        abilityName: ABILITY,
        parameters: { from: 'reminder', reminderId: notificationId }
      },
      actionButton: [
        {
          title: '关闭',
          type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE
        }
      ],
      ringDuration: 5,
      title: title,
      content: content,
      notificationId: notificationId,
      slotType: notificationManager.SlotType.SERVICE_INFORMATION
    };

    return await reminderAgentManager.publishReminder(req);
  }

  /** 取消定时提醒 */
  static async cancelSchedule(reminderId: number): Promise<void> {
    await reminderAgentManager.cancelReminder(reminderId);
  }

  /** 清角标:先清通知,再清数字,顺序不能反 */
  static async clearBadge(): Promise<void> {
    try {
      await notificationManager.cancelAll();
      await notificationManager.setBadgeNumber(0);
    } catch (e) {
      const err = e as BusinessError;
      hilog.error(0x0000, 'notify', `清角标失败 code=${err.code}`);
    }
  }
}

八、避坑速查

现象 大概率原因
写了 deliveryTime 但通知立刻弹出 deliveryTime 是系统生成的只读字段,不做定时
应用被杀后定时提醒不生效 用了普通通知而非代理提醒
publishReminder1700001 通知授权未获取
publishReminder1700002 有效提醒超过 30 条(非周期提醒用完没取消)
提醒只有状态栏图标,无横幅无声音 渠道选了 CONTENT_INFORMATION(LEVEL_MIN)
第二次点通知没反应 只写了 onCreate,漏了 onNewWant
点第二条通知进了第一条的详情 requestCode 固定且没带 UPDATE_PRESENT_FLAG
点击通知跳转失败、白屏 在 Ability 里直接 router.pushUrl,UI 尚未 ready
角标清零后又自动变回来 badgeNumber 累加与 setBadgeNumber 混用,存量通知把它顶回去
设了 badgeNumber: 0 但角标没清 0 属于"≤0 忽略本次设定",不是清零
部分机型角标不显示 系统"桌面角标"开关未开 / 该设备仅支持红点

小结

三个功能,本质上是三条不同的链路:

  • 定时消息 ------ 交给 reminderAgentManager,它由系统代理执行,不受进程存活影响。记得处理 30 条上限和 AGC 开放能力申请。
  • 点击路由 ------ 分清 WantAgent 实例(普通通知)和简化对象(代理提醒),冷热启动两条路径都写,跳转意图用 AppStorage 中转而不是在 Ability 里直接路由。
  • 桌面角标 ------ badgeNumber 累加、setBadgeNumber 设定,二选一;角标不会自动清除,得自己写清除逻辑。

参考文档

相关推荐
flash俊杰1 小时前
SQLite + sqlite-vec:100 篇文档内的私域知识库怎么做
前端
Hilaku1 小时前
为什么死磕 1px 的团队,用户体验反而更差?
前端·javascript·程序员
lichenyang4531 小时前
鸿蒙元服务跳转 H5 太慢?从 Web 内核预热、网络预连接到骨架屏的完整优化方案
前端
aa小小1 小时前
大屏的数据应该怎么做“实时更新“?大屏页面如果一直开着不关(比如挂了几天几夜),你觉得可能会出现什么问题,需要怎么预防?
前端·数据可视化
aa小小2 小时前
Proxy 响应式原理
前端·proxy
计算机魔术师2 小时前
Latent Space 访谈 TypeSafe CEO Diogo Almeida:Jev 是面向生产环境的 System One 模型而非万能 God 模型
前端
yzy852 小时前
Vue中下载或打开文件的JavaScript方法
前端·javascript·vue