OpenGraph

This commit is contained in:
2025-09-20 22:11:42 +08:00
parent b4c105b43e
commit dd6ff13228
16 changed files with 13280 additions and 26 deletions

View File

@@ -0,0 +1,161 @@
<script setup lang="ts">
import { useOgImageRuntimeConfig } from "#og-image/app/utils"
import { useSiteConfig } from "#site-config/app/composables"
import { computed, defineComponent, h, resolveComponent } from "vue"
const props = defineProps({
colorMode: { type: String, required: false },
title: { type: String, required: false, default: "title" },
description: { type: String, required: false },
icon: { type: [String, Boolean], required: false },
siteName: { type: String, required: false },
siteLogo: { type: String, required: false },
theme: { type: String, required: false, default: "#3f51b5" },
backgroundImage: { type: String, required: false }
})
const HexRegex = /^#(?:[0-9a-f]{3}){1,2}$/i
const runtimeConfig = useOgImageRuntimeConfig()
const colorMode = computed(() => {
return props.colorMode || runtimeConfig.colorPreference || "light"
})
const themeHex = computed(() => {
if (HexRegex.test(props.theme)) return props.theme
if (HexRegex.test(`#${props.theme}`)) return `#${props.theme}`
if (props.theme.startsWith("rgb")) {
const rgb = props.theme
.replace("rgb(", "")
.replace("rgba(", "")
.replace(")", "")
.split(",")
.map((v) => Number.parseInt(v.trim(), 10))
const hex = rgb
.map((v) => {
const hex2 = v.toString(16)
return hex2.length === 1 ? `0${hex2}` : hex2
})
.join("")
return `#${hex}`
}
return "#FFFFFF"
})
const themeRgb = computed(() => {
return themeHex.value
.replace("#", "")
.match(/.{1,2}/g)
?.map((v) => Number.parseInt(v, 16))
.join(", ")
})
const siteConfig = useSiteConfig()
const siteName = computed(() => {
return props.siteName || siteConfig.name
})
const siteLogo = computed(() => {
return props.siteLogo || siteConfig.logo
})
const IconComponent = runtimeConfig.hasNuxtIcon
? resolveComponent("Icon")
: defineComponent({
render() {
return h("div", "missing @nuxt/icon")
}
})
if (
typeof props.icon === "string" &&
!runtimeConfig.hasNuxtIcon &&
process.dev
) {
console.warn(
"Please install `@nuxt/icon` to use icons with the fallback OG Image component."
)
console.log("\nnpx nuxi module add icon\n")
}
</script>
<template>
<div
class="w-full h-full flex justify-between relative p-[60px]"
:class="[
colorMode === 'light'
? ['bg-white', 'text-gray-900']
: ['bg-gray-900', 'text-white']
]"
>
<div
v-if="backgroundImage"
class="absolute inset-0 w-full h-full bg-cover bg-center"
:style="{ backgroundImage: `url(${backgroundImage})` }"
></div>
<div
v-if="backgroundImage"
class="absolute inset-0 w-full h-full"
:class="colorMode === 'light' ? 'bg-white/80' : 'bg-gray-900/80'"
/>
<div
class="flex absolute top-0 right-[-100%]"
:style="{
width: '200%',
height: '200%',
backgroundImage: `radial-gradient(circle, rgba(${themeRgb}, 0.5) 0%, ${
colorMode === 'dark'
? 'rgba(5, 5, 5,0.3)'
: 'rgba(255, 255, 255, 0.7)'
} 50%, ${
props.colorMode === 'dark'
? 'rgba(5, 5, 5,0)'
: 'rgba(255, 255, 255, 0)'
} 70%)`
}"
/>
<div class="h-full w-full justify-between relative">
<div class="flex flex-row justify-between items-start">
<div class="flex flex-col w-full max-w-[65%]">
<h1
class="m-0 font-bold mb-[30px] text-[75px]"
style="display: block; text-overflow: ellipsis"
:style="{ lineClamp: description ? 2 : 3 }"
>
{{ title }}
</h1>
<p
v-if="description"
class="text-[35px] leading-12"
:class="[
colorMode === 'light' ? ['text-gray-700'] : ['text-gray-300']
]"
style="display: block; line-clamp: 3; text-overflow: ellipsis"
>
{{ description }}
</p>
</div>
<div v-if="Boolean(icon)" style="width: 30%" class="flex justify-end">
<IconComponent
:name="icon"
size="250px"
style="margin: 0 auto; opacity: 0.7"
/>
</div>
</div>
<div class="flex flex-row justify-center items-center text-left w-full">
<img v-if="siteLogo" :src="siteLogo" height="30" width="30" />
<template v-else>
<svg
height="50"
width="50"
class="mr-3"
viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg"
>
<path
:fill="theme.includes('#') ? theme : `#${theme}`"
d="M62.3,-53.9C74.4,-34.5,73.5,-9,67.1,13.8C60.6,36.5,48.7,56.5,30.7,66.1C12.7,75.7,-11.4,74.8,-31.6,65.2C-51.8,55.7,-67.9,37.4,-73.8,15.7C-79.6,-6,-75.1,-31.2,-61.1,-51C-47.1,-70.9,-23.6,-85.4,0.8,-86C25.1,-86.7,50.2,-73.4,62.3,-53.9Z"
transform="translate(100 100)"
/>
</svg>
<p v-if="siteName" style="font-size: 25px" class="font-bold">
{{ siteName }}
</p>
</template>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,152 @@
<script setup lang="ts">
import { useOgImageRuntimeConfig } from "#og-image/app/utils"
import { useSiteConfig } from "#site-config/app/composables"
import { computed, defineComponent, h, resolveComponent } from "vue"
const props = defineProps({
colorMode: { type: String, required: false },
title: { type: String, required: false, default: "title" },
description: { type: String, required: false },
icon: { type: [String, Boolean], required: false },
siteName: { type: String, required: false },
siteLogo: { type: String, required: false },
theme: { type: String, required: false, default: "#3f51b5" },
backgroundImage: { type: String, required: false },
avatarUrl: { type: String, required: false }
})
const HexRegex = /^#(?:[0-9a-f]{3}){1,2}$/i
const runtimeConfig = useOgImageRuntimeConfig()
const colorMode = computed(() => {
return props.colorMode || runtimeConfig.colorPreference || "light"
})
const themeHex = computed(() => {
if (HexRegex.test(props.theme)) return props.theme
if (HexRegex.test(`#${props.theme}`)) return `#${props.theme}`
if (props.theme.startsWith("rgb")) {
const rgb = props.theme
.replace("rgb(", "")
.replace("rgba(", "")
.replace(")", "")
.split(",")
.map((v) => Number.parseInt(v.trim(), 10))
const hex = rgb
.map((v) => {
const hex2 = v.toString(16)
return hex2.length === 1 ? `0${hex2}` : hex2
})
.join("")
return `#${hex}`
}
return "#FFFFFF"
})
const themeRgb = computed(() => {
return themeHex.value
.replace("#", "")
.match(/.{1,2}/g)
?.map((v) => Number.parseInt(v, 16))
.join(", ")
})
const siteConfig = useSiteConfig()
const siteName = computed(() => {
return props.siteName || siteConfig.name
})
const siteLogo = computed(() => {
return props.siteLogo || siteConfig.logo
})
const IconComponent = runtimeConfig.hasNuxtIcon
? resolveComponent("Icon")
: defineComponent({
render() {
return h("div", "missing @nuxt/icon")
}
})
if (
typeof props.icon === "string" &&
!runtimeConfig.hasNuxtIcon &&
process.dev
) {
console.warn(
"Please install `@nuxt/icon` to use icons with the fallback OG Image component."
)
console.log("\nnpx nuxi module add icon\n")
}
const apiBaseServer = useSolarNetworkUrl(true)
function toAbsoluteUrl(url: string | undefined) {
if (!url) return undefined
if (url.startsWith("http"))
return `${siteConfig.url}/__og/convert-image?url=${encodeURIComponent(url)}`
if (url.startsWith("/api"))
return `${siteConfig.url}/__og/convert-image?url=${encodeURIComponent(
`${apiBaseServer}${url.replace("/api", "")}`
)}`
return `${siteConfig.url}${url}`
}
</script>
<template>
<div
class="w-full h-full flex justify-between relative"
:class="[
...(colorMode === 'light'
? ['bg-white', 'text-gray-900']
: ['bg-gray-900', 'text-white'])
]"
>
<div
v-if="backgroundImage"
class="absolute inset-0 w-full h-full"
:class="colorMode === 'light' ? 'bg-white/80' : 'bg-gray-900/80'"
/>
<img
v-if="backgroundImage"
:src="toAbsoluteUrl(backgroundImage)"
class="absolute top-0 left-0 w-full h-full object-cover opacity-70"
style="min-width: 1200px; min-height: 600px;"
/>
<div class="h-full w-full justify-between relative p-[60px]">
<div class="flex flex-row justify-between items-start">
<div class="flex flex-col w-full max-w-[65%]">
<h1
class="m-0 font-bold mb-[30px] text-[75px]"
style="display: block; text-overflow: ellipsis"
:style="{ lineClamp: description ? 2 : 3 }"
>
{{ title }}
</h1>
<p
v-if="description"
class="text-[35px] leading-12"
:class="[
colorMode === 'light' ? ['text-gray-700'] : ['text-gray-300']
]"
style="display: block; line-clamp: 3; text-overflow: ellipsis"
>
{{ description }}
</p>
</div>
<div v-if="Boolean(icon)" style="width: 30%" class="flex justify-end">
<IconComponent
:name="icon"
size="250px"
style="margin: 0 auto; opacity: 0.7"
/>
</div>
</div>
<div class="flex flex-row justify-end items-center text-right gap-3 w-full">
<p v-if="siteName" style="font-size: 25px" class="font-bold">
{{ siteName }}
</p>
<img
v-if="avatarUrl"
:src="toAbsoluteUrl(avatarUrl)"
height="60"
width="60"
class="rounded-full mr-4"
/>
</div>
</div>
</div>
</template>