LogoFuyu Docs

图片生成

进行图片生成

介绍

图片生成主要依靠 satori 进行生成图片

可以将 Vue 模板渲染成图片

须知事项

  • x-satori 负责把最终的静态 HTML 渲染为图片,它不会执行或运行 Vue 组件中的 JavaScript(例如 <script> 内的逻辑、响应式状态或生命周期钩子),也不会处理组件范围内的 <style>(包括 scoped/style modules)。因此,仅依赖组件内脚本或样式来生成最终效果通常不会生效。

  • 由于 satori 并不完整地应用组件级样式,单纯依靠 class 引入的样式通常不可预期。satori 对部分 Tailwind CSS 类有内置支持,但支持范围有限且可能随版本变化,使用前请务必自行测试。如果你在 class 中使用 Tailwind,请在目标环境中验证样式是否生效。

  • 要保证样式在图片中生效,推荐将关键样式写为内联样式(例如 style="..."),或者在渲染前把模板预渲染为包含最终样式的静态 HTML 再交给 satori。可以参考 satori 的 CSS 支持列表来确认哪些 CSS 属性被支持

satori 支持的 CSS 列表(参考)

template.vue
<template>
  <!-- 不生效部分  -->
  <div class="box" style="display: flex;">
    {{ title }}
  </div>
  <div style="display: flex;">
  <!-- 不生效部分 -->
    {{ newtitle }}
  </div>
  <div v-for="value in fortest" :key="value" style="display: flex;">
    <p>value: {{ value }}</p>
  </div>
  <div v-if="show" style="display: flex;">隐藏内容</div>
</template>
<script setup lang="ts">
// 不生效部分
import { ref } from "vue";

// 这里是传递参数部分
defineProps<{
  title?: string;
  fortest?: number;
  show?: boolean;
}>();

// 不生效部分
const newtitle = ref("新标题");
</script>

// 不生效部分
{/* [!code highlight] */}
<style scoped>
.box {
  width: 100px; 
  height: 100px; 
  background-color: red; 
} 
</style>

使用方法

在 @function/genImg.ts 中 我们包装了一个 generateImage 方法用于图片生成

下面是 generateImage 方法的类型表,使用项目内置的 TypeTable 组件展示参数说明与返回值:

Prop

Type

示例:

test.ts
template.vue
test.ts
import template from "./template.vue?raw";
import { generateImage } from "@function/genImg.ts";
// 调用示例
const result = await generateImage(
  {
    width: 1200,
    height: 630,
    format: 'png',
    imgname: 'og-image',
  },
  template
  { title: '示例标题' },
);

返回处理

generateImage 方法返回一个包含生成图片信息的对象:

{
  path: '/tmp/og-image.png',
  width: 1200,
  height: 630,
  hash: 'abcd1234',
  file_id: 'file_5678'
}

Prop

Type

import { sendMessage } from "@TDLib/function/message.ts";
import { generateImage } from "@function/genImg.ts";
import { updateImgCache } from "@db/update.ts";
import fs from "fs";


const imagePath = await generateImage(
  {
    width: 1200,
    height: "auto",
  },
  templateStr,
  props
);
// 当有路径时表示图片生成成功并且没有缓存需要进行上传
if (imagePath.path) {
  const result = await sendMessage(client, message.message.chat_id, {
    media: {
      photo: {
        path: imagePath.path,
      },
    },
  });
  // 保存 file_id 到缓存
  if (result && result.content._ === "messagePhoto" && imagePath.hash) {
    const file_id = result.content.photo.sizes.slice(-1)[0].photo.remote.id;
    try {
      await updateImgCache(imagePath.hash, file_id);
    } catch (err) {
      logger.warn("保存 file_id 缓存失败", err);
    }
  }
  // 可选删除临时文件
  await fs.promises.unlink(imagePath.path);
  return;
}

// 当有缓存时path会为空,file_id 进行发送
if ((imagePath.hash, imagePath.file_id)) {
  await sendMessage(client, message.message.chat_id, {
    media: {
      photo: {
        id: imagePath.file_id,
      },
    },
  });
  return;
}
// 图片生成失败错误处理
await sendMessage(client, message.message.chat_id, {
  text: "图片生成失败",
});