update at 2026-02-07 11:14:09
This commit is contained in:
248
frontend/src/stores/fontStore.ts
Normal file
248
frontend/src/stores/fontStore.ts
Normal 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,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user