SwAsyncText

异步加载远程文本组件。URL 含 .bnc 时走 AES 解密路径,否则直接当 UTF-8 文本读取,业务方传 url 即可,加密分流由组件内部自动处理。fileId = md5(url.pathname) 写入 IndexedDB text 表持久化缓存,命中即返回;监听 url 变化自动重新加载,天然适配章节切换等场景。

基础用法

使用作用域插槽接收加载结果。text 为 null 时表示加载中,string 时表示已就绪(含缓存命中)。

基础用法(非加密)

加载中...
<template>
  <SwAsyncText :url="textUrl" :img-key="aesKey">
    <template #default="{ text }">
      <div v-if="text === null" class="loading">加载中…</div>
      <article v-else>{{ text }}</article>
    </template>
  </SwAsyncText>
</template>

<script setup lang="ts">
const textUrl = '/articles/welcome.txt'
const aesKey = useRuntimeConfig().public.cacheAesKey
</script>

加密内容(.bnc 分流)

下面是真实加密的演示资源(chapter-secret.bnc 用与 demo imgKey 配套的密钥加密):异步取回密文 → 客户端 AES 解密 → 展示明文。

加密内容:异步解密成功展示

加载中...

URL 含 .bnc 后缀时自动走 AES 解密路径。imgKey 沿用 SwImage 同名 prop,与 useSystemStore().info.img_key 字段对齐;非加密路径下 imgKey 仍需传值(满足 prop 必填),但内部不会被使用。

<template>
  <SwAsyncText :url="chapter.url" :img-key="aesKey">
    <template #default="{ text }">
      <div v-if="text === null" class="skeleton" />
      <article v-else v-html="text" />
    </template>
  </SwAsyncText>
</template>

<script setup lang="ts">
// chapter.url 例如:https://cdn.example.com/novel/chap-001.bnc
const aesKey = useRuntimeConfig().public.cacheAesKey
</script>

章节切换自动重新加载

url prop 变化时组件自动重新请求并解密,无需手动重置状态,适合翻页阅读器场景。

章节切换

加载中...

监听加载完成事件

@update:resp 在文本加载完成后触发(含缓存命中),用于字数统计、阅读进度上报等后续动作。

监听加载事件 + 字数统计

加载中...

Props

名称类型必填默认值说明
imgKeystring✓—AES 解密密钥。沿用 SwImage 同名 prop,与 useSystemStore().info.img_key 对齐;非加密路径下不会被使用,但仍需传值
urlstring''文本 URL。含 .bnc 走解密分支,否则直接当 UTF-8 文本读取

Events

事件名载荷说明
update:respresp: string文本加载完成(含缓存命中)后触发。从 null 变为 string 时只触发一次,再次切换 url 重新加载完成又会触发

Slots

插槽名作用域参数说明
default{ text: string | null }作用域插槽。text === null 表示加载中(或 SSR 阶段),string 表示已就绪

工作原理

  1. SSR 短路:import.meta.client 为 false 时直接返回 null,不发起任何网络请求与解密
  2. 缓存查询:fileId = md5(url.pathname),先查 IndexedDB text 表;命中直接 TextDecoder().decode(bytes) 返回
  3. 远程加载 + 条件解密:未命中则 fetch(url) 拿到 ArrayBuffer;URL 含 .bnc 走 aes.decrypt(buf, imgKey) → 否则直接 new Uint8Array(buf)
  4. 写缓存 + 通知:解密后的字节写入 IndexedDB(下次秒开),同时 result 从 null → string,触发 update:resp 事件
  5. 响应式重载:watchEffect 监听 props.url,变化时清状态重新走流程

底层通用逻辑封装在 useDecryptedAsset composable,SwAsyncText 与 SwImage 共享同一套 fetch + 条件解密 + 缓存 + 状态机。

注意事项

  • imgKey 建议通过 useRuntimeConfig().public.cacheAesKey 传入,不要硬编码密钥
  • 作用域插槽中 text === null 是加载中状态,text === '' 是"加载完成但内容为空字符串",注意区分
  • 组件仅在客户端执行,SSR 阶段插槽收到的 text 始终为 null,请提供骨架占位
  • update:resp 事件缓存命中也会触发——对调用方更一致(任何"加载完成"都通知),但首屏统计 PV 时要去重