update at 2026-02-07 11:14:09

This commit is contained in:
douboer
2026-02-07 11:14:09 +08:00
parent 2d18aa5137
commit 591bd9ba05
67 changed files with 4885 additions and 0 deletions

View File

@@ -0,0 +1,248 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { FontInfo, FontTreeNode } from '../types/font'
import { loadFontWithProgress } from '../utils/font-loader'
export const useFontStore = defineStore('font', () => {
function readSet(key: string): Set<string> {
try {
const raw = localStorage.getItem(key)
if (!raw) return new Set()
const parsed = JSON.parse(raw)
return Array.isArray(parsed) ? new Set(parsed.map(String)) : new Set()
} catch {
return new Set()
}
}
function writeSet(key: string, value: Set<string>) {
try {
localStorage.setItem(key, JSON.stringify(Array.from(value)))
} catch {
// ignore storage errors
}
}
// 状态
const fonts = ref<FontInfo[]>([])
const selectedFontIds = ref<Set<string>>(new Set())
const favoriteFontIds = ref<Set<string>>(readSet('font.favoriteFontIds'))
const previewFontIds = ref<Set<string>>(readSet('font.previewFontIds'))
const expandedCategoryNames = ref<Set<string>>(readSet('font.expandedCategories'))
const fontTree = ref<FontTreeNode[]>([])
const isLoadingFonts = ref(false)
// 计算属性
const selectedFonts = computed(() => {
return fonts.value.filter(f => selectedFontIds.value.has(f.id))
})
const favoriteFonts = computed(() => {
return fonts.value.filter(f => favoriteFontIds.value.has(f.id))
})
const previewFonts = computed(() => {
return fonts.value.filter(f => previewFontIds.value.has(f.id))
})
const favoriteTree = computed(() => {
return buildFontTree(favoriteFonts.value)
})
// 方法
function addFont(fontInfo: FontInfo) {
fonts.value.push(fontInfo)
}
function removeFont(fontId: string) {
const index = fonts.value.findIndex(f => f.id === fontId)
if (index !== -1) {
fonts.value.splice(index, 1)
}
selectedFontIds.value.delete(fontId)
favoriteFontIds.value.delete(fontId)
writeSet('font.favoriteFontIds', favoriteFontIds.value)
previewFontIds.value.delete(fontId)
writeSet('font.previewFontIds', previewFontIds.value)
}
function selectFont(fontId: string) {
selectedFontIds.value.add(fontId)
}
function unselectFont(fontId: string) {
selectedFontIds.value.delete(fontId)
}
function toggleSelectFont(fontId: string) {
if (selectedFontIds.value.has(fontId)) {
unselectFont(fontId)
} else {
selectFont(fontId)
}
}
function clearSelection() {
selectedFontIds.value.clear()
}
function favoriteFont(fontId: string) {
const font = fonts.value.find(f => f.id === fontId)
if (font) {
font.isFavorite = true
favoriteFontIds.value.add(fontId)
writeSet('font.favoriteFontIds', favoriteFontIds.value)
}
}
function unfavoriteFont(fontId: string) {
const font = fonts.value.find(f => f.id === fontId)
if (font) {
font.isFavorite = false
favoriteFontIds.value.delete(fontId)
writeSet('font.favoriteFontIds', favoriteFontIds.value)
}
}
function toggleFavorite(fontId: string) {
if (favoriteFontIds.value.has(fontId)) {
unfavoriteFont(fontId)
} else {
favoriteFont(fontId)
}
}
function addToPreview(fontId: string) {
previewFontIds.value.add(fontId)
writeSet('font.previewFontIds', previewFontIds.value)
}
function removeFromPreview(fontId: string) {
previewFontIds.value.delete(fontId)
writeSet('font.previewFontIds', previewFontIds.value)
}
function togglePreview(fontId: string) {
if (previewFontIds.value.has(fontId)) {
removeFromPreview(fontId)
} else {
addToPreview(fontId)
}
}
function clearPreview() {
previewFontIds.value.clear()
writeSet('font.previewFontIds', previewFontIds.value)
}
function setCategoryExpanded(categoryName: string, expanded: boolean) {
const next = new Set(expandedCategoryNames.value)
if (expanded) {
next.add(categoryName)
} else {
next.delete(categoryName)
}
expandedCategoryNames.value = next
writeSet('font.expandedCategories', expandedCategoryNames.value)
}
async function loadFont(fontInfo: FontInfo) {
if (fontInfo.loaded || !fontInfo.path) {
return
}
try {
const font = await loadFontWithProgress(fontInfo.path, (progress) => {
fontInfo.progress = progress
})
fontInfo.font = font
fontInfo.loaded = true
fontInfo.progress = 100
} catch (error) {
console.error(`Failed to load font ${fontInfo.name}:`, error)
throw error
}
}
function buildFontTree(fontList: FontInfo[]): FontTreeNode[] {
const tree: FontTreeNode[] = []
const categoryMap = new Map<string, FontTreeNode>()
for (const font of fontList) {
let categoryNode = categoryMap.get(font.category)
if (!categoryNode) {
categoryNode = {
name: font.category,
type: 'category',
children: [],
expanded: expandedCategoryNames.value.has(font.category),
selected: false,
}
categoryMap.set(font.category, categoryNode)
tree.push(categoryNode)
}
const fontNode: FontTreeNode = {
name: font.name,
type: 'font',
fontInfo: font,
expanded: false,
selected: selectedFontIds.value.has(font.id),
}
categoryNode.children!.push(fontNode)
}
// 按类别名称排序
tree.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'))
// 每个类别内的字体按名称排序
for (const category of tree) {
if (category.children) {
category.children.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'))
}
}
return tree
}
function updateFontTree() {
fontTree.value = buildFontTree(fonts.value)
}
return {
// 状态
fonts,
selectedFontIds,
favoriteFontIds,
previewFontIds,
expandedCategoryNames,
fontTree,
isLoadingFonts,
// 计算属性
selectedFonts,
favoriteFonts,
previewFonts,
favoriteTree,
// 方法
addFont,
removeFont,
selectFont,
unselectFont,
toggleSelectFont,
clearSelection,
favoriteFont,
unfavoriteFont,
toggleFavorite,
addToPreview,
removeFromPreview,
togglePreview,
clearPreview,
setCategoryExpanded,
loadFont,
updateFontTree,
}
})