feat(font): 字体分为内置/链接两种来源,链接可存多套并启用
- 设置改为 font.source(builtin|custom) + current + items[] - 每条链接含 key/name/url/family;后台可加、改、删,选一条启用 - 前端按启用的那条注入 @font-face / CSS 链接,删除当前项时退回第一条 - 校验:条数上限 20、key 唯一、url 仅 http(s):// 或 /fonts/
This commit is contained in:
@@ -43,6 +43,8 @@ import {
|
||||
saveSettings,
|
||||
TONE_OPTIONS,
|
||||
type BackgroundSettings,
|
||||
type FontLink,
|
||||
type FontSource,
|
||||
type ProfileSettings,
|
||||
type SearchSettings,
|
||||
type SettingsDocument,
|
||||
@@ -2309,7 +2311,29 @@ function SystemPanel({
|
||||
setNext('')
|
||||
}
|
||||
|
||||
const localFont = font.url.startsWith('/fonts/') ? font.url : ''
|
||||
const activeKey = font.items.some((item) => item.key === font.current)
|
||||
? font.current
|
||||
: (font.items[0]?.key ?? '')
|
||||
const update = (patch: Partial<SiteSettings['font']>) => onChangeFont({ ...font, ...patch })
|
||||
const updateItem = (key: string, patch: Partial<FontLink>) =>
|
||||
update({ items: font.items.map((item) => (item.key === key ? { ...item, ...patch } : item)) })
|
||||
const removeItem = (key: string) => {
|
||||
const items = font.items.filter((item) => item.key !== key)
|
||||
onChangeFont({
|
||||
...font,
|
||||
items,
|
||||
current: font.current === key ? (items[0]?.key ?? '') : font.current,
|
||||
})
|
||||
}
|
||||
const addItem = (url: string, name: string) => {
|
||||
const key = `font-${Date.now().toString(36)}-${font.items.length}`
|
||||
onChangeFont({
|
||||
...font,
|
||||
source: 'custom',
|
||||
items: [...font.items, { key, name, url, family: '' }],
|
||||
current: font.current === '' ? key : font.current,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Space direction="column" gap="lg" align="stretch" className="admin-form">
|
||||
@@ -2317,48 +2341,100 @@ function SystemPanel({
|
||||
正文字体
|
||||
</Text>
|
||||
<Text size="sm" tone="muted">
|
||||
说明:字体文件放进程序同级的 font/ 目录,这里会自动列出来;也可以直接填字体文件或字体 CSS
|
||||
的链接。留空使用内置的霞鹜文楷。
|
||||
说明:内置是霞鹜文楷。选「字体链接」后,可以在下面存几套常用字体(font/ 目录里的文件或外部
|
||||
链接),需要哪个就启用哪个,不用每次重填。
|
||||
</Text>
|
||||
<FormField
|
||||
label="font 目录里的字体"
|
||||
htmlFor="font-file"
|
||||
hint={fonts.length === 0 ? 'font/ 目录里还没有字体文件' : '选中的字体通过 /fonts/ 对外提供'}
|
||||
>
|
||||
<FormField label="字体来源" htmlFor="font-source">
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: '使用内置字体(霞鹜文楷)' },
|
||||
...fonts.map((file) => ({ value: file.url, label: file.name })),
|
||||
{ value: 'builtin', label: '内置(霞鹜文楷)' },
|
||||
{ value: 'custom', label: '字体链接' },
|
||||
]}
|
||||
value={localFont}
|
||||
onChange={(url) => onChangeFont({ ...font, url })}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
label="或:字体链接"
|
||||
htmlFor="font-url"
|
||||
hint="woff2 / woff / ttf / otf 文件,或字体 CSS 链接;填了以这里为准"
|
||||
>
|
||||
<Input
|
||||
id="font-url"
|
||||
value={font.url}
|
||||
placeholder="https://… 或 /fonts/xxx.woff2"
|
||||
onChange={(event) => onChangeFont({ ...font, url: event.target.value })}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
label="字体族名"
|
||||
htmlFor="font-family"
|
||||
hint="链接到 CSS 时填 CSS 里声明的 font-family;字体文件可留空"
|
||||
>
|
||||
<Input
|
||||
id="font-family"
|
||||
value={font.family}
|
||||
placeholder="例如 LXGW Neo XiHei"
|
||||
onChange={(event) => onChangeFont({ ...font, family: event.target.value })}
|
||||
value={font.source}
|
||||
onChange={(source) => update({ source: source as FontSource })}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{font.source === 'custom' && (
|
||||
<>
|
||||
<FormField
|
||||
label="当前使用的字体"
|
||||
htmlFor="font-current"
|
||||
hint={font.items.length === 0 ? '还没有字体,先在下面添加一条' : '从已保存的字体里选一个启用'}
|
||||
>
|
||||
<Select
|
||||
options={font.items.map((item) => ({
|
||||
value: item.key,
|
||||
label: item.name === '' ? item.key : item.name,
|
||||
}))}
|
||||
value={activeKey}
|
||||
onChange={(current) => update({ current })}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
{font.items.map((item) => (
|
||||
<div className="font-link" key={item.key}>
|
||||
<div className="admin-row">
|
||||
<FormField label="名称" htmlFor={`font-name-${item.key}`}>
|
||||
<Input
|
||||
id={`font-name-${item.key}`}
|
||||
value={item.name}
|
||||
placeholder="例如 观感字体"
|
||||
onChange={(event) => updateItem(item.key, { name: event.target.value })}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
label="字体族名"
|
||||
htmlFor={`font-family-${item.key}`}
|
||||
hint="CSS 链接时填 CSS 里声明的 font-family"
|
||||
>
|
||||
<Input
|
||||
id={`font-family-${item.key}`}
|
||||
value={item.family}
|
||||
placeholder="字体文件可留空"
|
||||
onChange={(event) => updateItem(item.key, { family: event.target.value })}
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
<FormField
|
||||
label="字体链接"
|
||||
htmlFor={`font-url-${item.key}`}
|
||||
hint="woff2 / woff / ttf / otf 文件,或字体 CSS 链接"
|
||||
>
|
||||
<Input
|
||||
id={`font-url-${item.key}`}
|
||||
value={item.url}
|
||||
placeholder="/fonts/xxx.woff2 或 https://…"
|
||||
onChange={(event) => updateItem(item.key, { url: event.target.value })}
|
||||
/>
|
||||
</FormField>
|
||||
<Space gap="sm" justify="end">
|
||||
<TextButton onClick={() => removeItem(item.key)}>删除这条</TextButton>
|
||||
</Space>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Space gap="sm">
|
||||
<Button variant="secondary" onClick={() => addItem('', '新字体')}>
|
||||
添加字体
|
||||
</Button>
|
||||
{fonts.length > 0 && (
|
||||
<Select
|
||||
options={[
|
||||
{ value: '', label: '从 font 目录添加…' },
|
||||
...fonts.map((file) => ({ value: file.url, label: file.name })),
|
||||
]}
|
||||
value=""
|
||||
onChange={(url) => {
|
||||
const file = fonts.find((entry) => entry.url === url)
|
||||
if (file !== undefined) addItem(file.url, file.name.replace(/\.[^.]+$/, ''))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
</>
|
||||
)}
|
||||
|
||||
<FormField label="当前密码" htmlFor="current-password">
|
||||
<PasswordInput
|
||||
id="current-password"
|
||||
|
||||
Reference in New Issue
Block a user