mirror of
https://github.com/2234839/web-font.git
synced 2026-07-13 20:21:07 +08:00
feat: 前端格式选择器组件,服务端支持能力透传
- 新增 FontSelector.tsx 独立组件,字体和输出格式并排选择 - /api/config 新增 supportedOutTypes 字段(LLRT 仅 ttf) - 前端加载配置后自动适配可用格式 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
820fc71166
commit
e2cc7c1144
@ -199,11 +199,14 @@ async function handleListFonts(req: Request, res: Response) {
|
|||||||
|
|
||||||
/** GET /api/config — 返回公开配置 */
|
/** GET /api/config — 返回公开配置 */
|
||||||
async function handleGetConfig(req: Request, res: Response) {
|
async function handleGetConfig(req: Request, res: Response) {
|
||||||
|
const isLlrt = release_name === "llrt";
|
||||||
return {
|
return {
|
||||||
req,
|
req,
|
||||||
res: jsonResponse({
|
res: jsonResponse({
|
||||||
enableTempUpload,
|
enableTempUpload,
|
||||||
adminUploadEnabled: !!adminApiKey,
|
adminUploadEnabled: !!adminApiKey,
|
||||||
|
/** LLRT 不支持 wasm,无法输出 woff2 */
|
||||||
|
supportedOutTypes: isLlrt ? ["ttf"] : ["woff2", "ttf"],
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "webfont",
|
"name": "webfont",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.3.1",
|
"version": "1.3.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpx tsx scripts/dev-all.ts",
|
"dev": "pnpx tsx scripts/dev-all.ts",
|
||||||
|
|||||||
38
src/App.tsx
38
src/App.tsx
@ -1,6 +1,7 @@
|
|||||||
import { createMemo, createSignal, onMount, Show, For } from "solid-js";
|
import { createMemo, createSignal, onMount, Show, For } from "solid-js";
|
||||||
import { fetchFonts, fetchConfig, type FontInfo, type ServerConfig } from "./api";
|
import { fetchFonts, fetchConfig, type FontInfo, type ServerConfig } from "./api";
|
||||||
import UploadSection from "./UploadSection";
|
import UploadSection from "./UploadSection";
|
||||||
|
import { SelectorRow } from "./FontSelector";
|
||||||
|
|
||||||
const s = {
|
const s = {
|
||||||
wrap: {
|
wrap: {
|
||||||
@ -92,9 +93,11 @@ function App() {
|
|||||||
const [text, set_text] = createSignal("天地无极,乾坤借法");
|
const [text, set_text] = createSignal("天地无极,乾坤借法");
|
||||||
const [fonts, set_fonts] = createSignal<FontInfo[]>([]);
|
const [fonts, set_fonts] = createSignal<FontInfo[]>([]);
|
||||||
const [selectedFont, set_selectedFont] = createSignal("");
|
const [selectedFont, set_selectedFont] = createSignal("");
|
||||||
|
const [outType, set_outType] = createSignal<"woff2" | "ttf">("woff2");
|
||||||
const [serverConfig, set_serverConfig] = createSignal<ServerConfig>({
|
const [serverConfig, set_serverConfig] = createSignal<ServerConfig>({
|
||||||
enableTempUpload: false,
|
enableTempUpload: false,
|
||||||
adminUploadEnabled: false,
|
adminUploadEnabled: false,
|
||||||
|
supportedOutTypes: ["woff2", "ttf"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const SLOGAN = "如清风似闪电,超级快的字体子集化裁剪";
|
const SLOGAN = "如清风似闪电,超级快的字体子集化裁剪";
|
||||||
@ -103,6 +106,10 @@ function App() {
|
|||||||
const [fontList, config] = await Promise.all([fetchFonts().catch(() => []), fetchConfig().catch(() => ({ enableTempUpload: false, adminUploadEnabled: false }))]);
|
const [fontList, config] = await Promise.all([fetchFonts().catch(() => []), fetchConfig().catch(() => ({ enableTempUpload: false, adminUploadEnabled: false }))]);
|
||||||
set_fonts(fontList);
|
set_fonts(fontList);
|
||||||
set_serverConfig(config);
|
set_serverConfig(config);
|
||||||
|
/** 服务端不支持当前 outType 时自动回退 */
|
||||||
|
if (!config.supportedOutTypes?.includes(outType())) {
|
||||||
|
set_outType(config.supportedOutTypes?.[0] || "ttf");
|
||||||
|
}
|
||||||
if (fontList.length > 0) {
|
if (fontList.length > 0) {
|
||||||
/** 标语随机使用一个字体展示 */
|
/** 标语随机使用一个字体展示 */
|
||||||
const randomFont = fontList[Math.floor(Math.random() * fontList.length)];
|
const randomFont = fontList[Math.floor(Math.random() * fontList.length)];
|
||||||
@ -123,10 +130,11 @@ function App() {
|
|||||||
|
|
||||||
const cssStyle = createMemo(() => {
|
const cssStyle = createMemo(() => {
|
||||||
const font = selectedFont();
|
const font = selectedFont();
|
||||||
|
const ot = outType();
|
||||||
if (!font) return "";
|
if (!font) return "";
|
||||||
return `@font-face {
|
return `@font-face {
|
||||||
font-family: "CustomFont";
|
font-family: "CustomFont";
|
||||||
src: url("${location.origin}/api?font=${font}&text=${encodeURIComponent(text())}&outType=woff2") format("woff2");
|
src: url("${location.origin}/api?font=${font}&text=${encodeURIComponent(text())}&outType=${ot}") format("${ot}");
|
||||||
}
|
}
|
||||||
.custom-font {
|
.custom-font {
|
||||||
color: red;
|
color: red;
|
||||||
@ -189,21 +197,14 @@ function App() {
|
|||||||
<p id="slogan" style={s.desc}>{SLOGAN}</p>
|
<p id="slogan" style={s.desc}>{SLOGAN}</p>
|
||||||
|
|
||||||
<section style={s.section}>
|
<section style={s.section}>
|
||||||
<label style={s.label}>选择字体</label>
|
<SelectorRow
|
||||||
<select
|
fonts={fonts()}
|
||||||
style={s.select}
|
selectedFont={selectedFont()}
|
||||||
value={selectedFont()}
|
onFontChange={onFontChange}
|
||||||
onChange={(e) => onFontChange(e.target.value)}
|
supportedOutTypes={serverConfig().supportedOutTypes || ["woff2", "ttf"]}
|
||||||
>
|
outType={outType()}
|
||||||
<option value="">-- 请选择 --</option>
|
onOutTypeChange={set_outType}
|
||||||
<For each={fonts()}>
|
/>
|
||||||
{(f) => (
|
|
||||||
<option value={f.name}>
|
|
||||||
{f.name} ({f.dir})
|
|
||||||
</option>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</select>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section style={s.section}>
|
<section style={s.section}>
|
||||||
@ -228,9 +229,10 @@ function App() {
|
|||||||
<button
|
<button
|
||||||
style={{ ...s.btn, padding: "3px 12px", "font-size": "12px" }}
|
style={{ ...s.btn, padding: "3px 12px", "font-size": "12px" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
const ot = outType();
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.href = `/api?font=${selectedFont()}&text=${encodeURIComponent(text())}&outType=woff2`;
|
a.href = `/api?font=${selectedFont()}&text=${encodeURIComponent(text())}&outType=${ot}`;
|
||||||
a.download = selectedFont().replace(/\.[^.]+$/, "") + "_subset.woff2";
|
a.download = selectedFont().replace(/\.[^.]+$/, "") + `_subset.${ot}`;
|
||||||
a.click();
|
a.click();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
118
src/FontSelector.tsx
Normal file
118
src/FontSelector.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { For } from "solid-js";
|
||||||
|
|
||||||
|
interface FontSelectorProps {
|
||||||
|
fonts: Array<{ name: string; dir: string }>;
|
||||||
|
selectedFont: string;
|
||||||
|
onFontChange: (font: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OutTypeSelectorProps {
|
||||||
|
supportedOutTypes: ("woff2" | "ttf")[];
|
||||||
|
outType: "woff2" | "ttf";
|
||||||
|
onOutTypeChange: (type: "woff2" | "ttf") => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const outTypeLabels: Record<string, string> = {
|
||||||
|
woff2: "WOFF2 体积更小",
|
||||||
|
ttf: "TTF 速度更快",
|
||||||
|
};
|
||||||
|
|
||||||
|
const outTypeDescs: Record<string, string> = {
|
||||||
|
woff2: "约压缩 50%,适合生产",
|
||||||
|
ttf: "无编码开销,适合开发",
|
||||||
|
};
|
||||||
|
|
||||||
|
const s = {
|
||||||
|
wrap: {
|
||||||
|
display: "flex",
|
||||||
|
gap: "12px",
|
||||||
|
} as const,
|
||||||
|
col: {
|
||||||
|
flex: 1,
|
||||||
|
} as const,
|
||||||
|
label: {
|
||||||
|
display: "block",
|
||||||
|
"font-size": "13px",
|
||||||
|
color: "#555",
|
||||||
|
"margin-bottom": "6px",
|
||||||
|
} as const,
|
||||||
|
select: {
|
||||||
|
width: "100%",
|
||||||
|
padding: "8px 12px",
|
||||||
|
"font-size": "14px",
|
||||||
|
border: "1px solid #d9d9d9",
|
||||||
|
"border-radius": "6px",
|
||||||
|
outline: "none",
|
||||||
|
"box-sizing": "border-box",
|
||||||
|
cursor: "pointer",
|
||||||
|
appearance: "none",
|
||||||
|
"background-image": "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2 4l4 4 4-4' fill='none' stroke='%23999' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E\")",
|
||||||
|
"background-repeat": "no-repeat",
|
||||||
|
"background-position": "right 10px center",
|
||||||
|
"padding-right": "28px",
|
||||||
|
} as const,
|
||||||
|
desc: {
|
||||||
|
"font-size": "11px",
|
||||||
|
color: "#bbb",
|
||||||
|
"margin-top": "4px",
|
||||||
|
} as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FontSelector(props: FontSelectorProps) {
|
||||||
|
return (
|
||||||
|
<div style={s.col}>
|
||||||
|
<label style={s.label}>选择字体</label>
|
||||||
|
<select
|
||||||
|
style={s.select}
|
||||||
|
value={props.selectedFont}
|
||||||
|
onChange={(e) => props.onFontChange(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">-- 请选择 --</option>
|
||||||
|
<For each={props.fonts}>
|
||||||
|
{(f) => (
|
||||||
|
<option value={f.name}>
|
||||||
|
{f.name}
|
||||||
|
</option>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OutTypeSelector(props: OutTypeSelectorProps) {
|
||||||
|
return (
|
||||||
|
<div style={{ width: "160px" }}>
|
||||||
|
<label style={s.label}>输出格式</label>
|
||||||
|
<select
|
||||||
|
style={s.select}
|
||||||
|
value={props.outType}
|
||||||
|
onChange={(e) => props.onOutTypeChange(e.target.value as "woff2" | "ttf")}
|
||||||
|
>
|
||||||
|
<For each={props.supportedOutTypes}>
|
||||||
|
{(t) => (
|
||||||
|
<option value={t}>{outTypeLabels[t]}</option>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</select>
|
||||||
|
<p style={s.desc}>{outTypeDescs[props.outType]}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SelectorRow(props: FontSelectorProps & OutTypeSelectorProps) {
|
||||||
|
return (
|
||||||
|
<div style={s.wrap}>
|
||||||
|
<FontSelector
|
||||||
|
fonts={props.fonts}
|
||||||
|
selectedFont={props.selectedFont}
|
||||||
|
onFontChange={props.onFontChange}
|
||||||
|
/>
|
||||||
|
<OutTypeSelector
|
||||||
|
supportedOutTypes={props.supportedOutTypes}
|
||||||
|
outType={props.outType}
|
||||||
|
onOutTypeChange={props.onOutTypeChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ export interface FontInfo {
|
|||||||
export interface ServerConfig {
|
export interface ServerConfig {
|
||||||
enableTempUpload: boolean;
|
enableTempUpload: boolean;
|
||||||
adminUploadEnabled: boolean;
|
adminUploadEnabled: boolean;
|
||||||
|
supportedOutTypes: ("woff2" | "ttf")[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UploadResult {
|
export interface UploadResult {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user