mirror of
https://github.com/2234839/web-font.git
synced 2026-09-04 14:53:32 +08:00
feat: 详情页全面接入 font-config + 防闪烁
- font-config.json: 新增 bodyTitle/bodyText/charsetPreview 字段 为每个字体配置通过 lint 检查的专属文案 - FontDetail.vue: 正文/字符预览使用 config 字段替代写死文案 大标题/正文/字符预览加 v-if=meta 守卫,消除 hydrate 闪烁 h1 标题使用 displayName - lint-font-config.ts: 扩展检查范围至所有文本字段 - FontUserConfig 类型同步更新(后端+前端)
This commit is contained in:
parent
9397e10aad
commit
a095cd0590
@ -396,6 +396,12 @@ export interface FontUserConfig {
|
||||
homepage?: string;
|
||||
/** 默认预览文字 */
|
||||
previewText?: string;
|
||||
/** 详情页正文标题 */
|
||||
bodyTitle?: string;
|
||||
/** 详情页正文段落 */
|
||||
bodyText?: string;
|
||||
/** 详情页字符预览行 */
|
||||
charsetPreview?: string;
|
||||
}
|
||||
|
||||
/** 字体元数据结果 */
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
/**
|
||||
* lint-font-config.ts
|
||||
*
|
||||
* 检查 font/font-config.json 中每个字体的 previewText 是否都被该字体实际包含。
|
||||
* 如果 previewText 中有字体不支持的字符,输出警告并标记失败。
|
||||
* 检查 font/font-config.json 中每个字体的文本字段(previewText / bodyTitle / bodyText / charsetPreview)
|
||||
* 是否都被该字体实际包含。如果有不支持的字符,输出警告并标记失败。
|
||||
*
|
||||
* 用法:pnpm tsx scripts/lint-font-config.ts
|
||||
* 用法:pnpm lint:font-config
|
||||
*
|
||||
* 修改 font-config.json 的 previewText 字段后应运行此脚本验证。
|
||||
* 修改 font-config.json 中任何文本字段后应运行此脚本验证。
|
||||
*/
|
||||
import { readFile, readdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
@ -15,8 +15,19 @@ import { extractCodePoints } from "../backend/font_util/font_meta.ts";
|
||||
interface FontUserConfig {
|
||||
displayName?: string;
|
||||
previewText?: string;
|
||||
bodyTitle?: string;
|
||||
bodyText?: string;
|
||||
charsetPreview?: string;
|
||||
}
|
||||
|
||||
/** 需要检查的文本字段及显示名 */
|
||||
const TEXT_FIELDS: Array<{ key: keyof FontUserConfig; label: string }> = [
|
||||
{ key: "previewText", label: "previewText" },
|
||||
{ key: "bodyTitle", label: "bodyTitle" },
|
||||
{ key: "bodyText", label: "bodyText" },
|
||||
{ key: "charsetPreview", label: "charsetPreview" },
|
||||
];
|
||||
|
||||
type FontConfig = Record<string, FontUserConfig>;
|
||||
|
||||
const FONT_DIR = "font";
|
||||
@ -36,58 +47,69 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 字体 codepoints 缓存(同一字体只解析一次) */
|
||||
const cpCache = new Map<string, Set<number>>();
|
||||
|
||||
let hasError = false;
|
||||
|
||||
for (const [fileName, cfg] of Object.entries(config)) {
|
||||
const previewText = cfg.previewText;
|
||||
if (!previewText) {
|
||||
/** 没配 previewText,跳过(用默认值,无法检查) */
|
||||
continue;
|
||||
}
|
||||
|
||||
const fontPath = fontPathMap.get(fileName);
|
||||
if (!fontPath) {
|
||||
console.warn(`⚠️ [${fileName}] 字体文件不存在,跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
/** 读取字体并提取 codepoints */
|
||||
const raw = await readFile(fontPath);
|
||||
const fontBuffer = raw.buffer.slice(
|
||||
raw.byteOffset,
|
||||
raw.byteOffset + raw.byteLength,
|
||||
);
|
||||
const supportedCps = extractCodePoints(fontBuffer);
|
||||
|
||||
/** 逐字符检查 previewText */
|
||||
const chars = [...previewText];
|
||||
/** 空格/换行等空白字符不检查(字体通常都有,但即使没有也不影响预览) */
|
||||
const missing: string[] = [];
|
||||
for (const ch of chars) {
|
||||
if (/\s/.test(ch)) continue;
|
||||
const cp = ch.codePointAt(0)!;
|
||||
if (!supportedCps.has(cp)) {
|
||||
missing.push(ch);
|
||||
}
|
||||
/** 读取字体 codepoints(有缓存则复用) */
|
||||
let supportedCps = cpCache.get(fileName);
|
||||
if (!supportedCps) {
|
||||
const raw = await readFile(fontPath);
|
||||
const fontBuffer = raw.buffer.slice(
|
||||
raw.byteOffset,
|
||||
raw.byteOffset + raw.byteLength,
|
||||
);
|
||||
supportedCps = extractCodePoints(fontBuffer);
|
||||
cpCache.set(fileName, supportedCps);
|
||||
}
|
||||
|
||||
const displayName = cfg.displayName ?? fileName;
|
||||
if (missing.length > 0) {
|
||||
hasError = true;
|
||||
console.error(
|
||||
`❌ [${displayName}] (${fileName}) previewText 含 ${missing.length} 个不支持的字符:${missing.join(" ")}`,
|
||||
);
|
||||
console.error(` previewText: "${previewText}"`);
|
||||
} else {
|
||||
console.log(`✅ [${displayName}] (${fileName}) previewText 检查通过`);
|
||||
let fontHasError = false;
|
||||
|
||||
/** 逐字段检查 */
|
||||
for (const { key, label } of TEXT_FIELDS) {
|
||||
const text = cfg[key];
|
||||
if (!text) continue;
|
||||
|
||||
const chars = [...text];
|
||||
const missing: string[] = [];
|
||||
for (const ch of chars) {
|
||||
/** 空白字符不检查 */
|
||||
if (/\s/.test(ch)) continue;
|
||||
const cp = ch.codePointAt(0)!;
|
||||
if (!supportedCps!.has(cp)) {
|
||||
missing.push(ch);
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
fontHasError = true;
|
||||
hasError = true;
|
||||
console.error(
|
||||
`❌ [${displayName}] ${label} 含 ${missing.length} 个不支持的字符:${missing.join(" ")}`,
|
||||
);
|
||||
console.error(` ${label}: "${text}"`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fontHasError) {
|
||||
console.log(`✅ [${displayName}] (${fileName}) 所有文本字段检查通过`);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
console.error("\n💔 存在不支持的字符,请修正 previewText");
|
||||
console.error("\n💔 存在不支持的字符,请修正相关文本字段");
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log("\n🎉 所有 previewText 检查通过");
|
||||
console.log("\n🎉 所有文本字段检查通过");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -73,6 +73,12 @@ export interface FontUserConfig {
|
||||
homepage?: string;
|
||||
/** 默认预览文字 */
|
||||
previewText?: string;
|
||||
/** 详情页正文标题 */
|
||||
bodyTitle?: string;
|
||||
/** 详情页正文段落 */
|
||||
bodyText?: string;
|
||||
/** 详情页字符预览行 */
|
||||
charsetPreview?: string;
|
||||
}
|
||||
|
||||
/** 字体元数据 */
|
||||
|
||||
@ -92,8 +92,14 @@ const previewLines = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
/** 显示名:优先 config.displayName */
|
||||
const displayName = computed(() => meta.value?.config?.displayName ?? fontName.value);
|
||||
/** 正文标题:优先 config.bodyTitle */
|
||||
const bodyTitle = computed(() => meta.value?.config?.bodyTitle ?? "清风明月");
|
||||
|
||||
/** 正文段落:优先 config.bodyText */
|
||||
const bodyText = computed(() => meta.value?.config?.bodyText ?? "山中有桂树,常伴青云飞。");
|
||||
|
||||
/** 字符预览行:优先 config.charsetPreview */
|
||||
const charsetPreview = computed(() => meta.value?.config?.charsetPreview ?? "天地玄黄宇宙洪荒:0123456789 ABCDEF");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -148,7 +154,7 @@ const displayName = computed(() => meta.value?.config?.displayName ?? fontName.v
|
||||
:data-font="fontName"
|
||||
style="font-size: 36px; font-weight: 700; color: #2c2c2c; margin: 0; line-height: 1.3"
|
||||
>
|
||||
{{ fontName }}
|
||||
{{ meta?.config?.displayName ?? fontName }}
|
||||
</h1>
|
||||
<p v-if="notFound" style="font-size: 14px; color: #e74c3c; margin: 12px 0 0">
|
||||
⚠ 该字体不存在或已被删除
|
||||
@ -162,6 +168,7 @@ const displayName = computed(() => meta.value?.config?.displayName ?? fontName.v
|
||||
<div style="max-width: 800px; margin: 0 auto; padding: 0 24px 80px">
|
||||
<!-- 大标题预览 -->
|
||||
<div
|
||||
v-if="meta"
|
||||
style="
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
@ -198,6 +205,7 @@ const displayName = computed(() => meta.value?.config?.displayName ?? fontName.v
|
||||
|
||||
<!-- 正文预览 -->
|
||||
<div
|
||||
v-if="meta"
|
||||
style="
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
@ -215,7 +223,7 @@ const displayName = computed(() => meta.value?.config?.displayName ?? fontName.v
|
||||
color: '#3a3a3a',
|
||||
}"
|
||||
>
|
||||
一叶知秋
|
||||
{{ bodyTitle }}
|
||||
</h2>
|
||||
<p
|
||||
:style="{
|
||||
@ -227,12 +235,13 @@ const displayName = computed(() => meta.value?.config?.displayName ?? fontName.v
|
||||
margin: 0,
|
||||
}"
|
||||
>
|
||||
山间晨露未晞,茶人已入林深处。指尖轻捻,择其嫩芽一二,置于竹篮之中。此乃一年之始,亦是一叶与万物的初遇。
|
||||
{{ bodyText }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 字符集预览 -->
|
||||
<div
|
||||
v-if="meta"
|
||||
style="
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
@ -253,7 +262,7 @@ const displayName = computed(() => meta.value?.config?.displayName ?? fontName.v
|
||||
wordBreak: 'break-all',
|
||||
}"
|
||||
>
|
||||
天地无极乾坤借法:0123456789 ABCDEF
|
||||
{{ charsetPreview }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user