update at 2026-02-07 15:41:30

This commit is contained in:
douboer
2026-02-07 15:41:30 +08:00
parent 0f7ade6945
commit 0b2595b0e0
4 changed files with 112 additions and 21 deletions

View File

@@ -321,7 +321,6 @@ console.log('App.vue: script setup completed')
<div class="flex flex-col gap-2 shrink-0 overflow-hidden" style="flex-basis: 400px; max-width: 480px; min-width: 320px;"> <div class="flex flex-col gap-2 shrink-0 overflow-hidden" style="flex-basis: 400px; max-width: 480px; min-width: 320px;">
<!-- Frame 5: 字体选择 - 弹性高度 --> <!-- Frame 5: 字体选择 - 弹性高度 -->
<div class="flex-[2] border border-solid border-[#f7e0e0] rounded-lg p-1.5 flex flex-col gap-2 overflow-hidden min-h-0"> <div class="flex-[2] border border-solid border-[#f7e0e0] rounded-lg p-1.5 flex flex-col gap-2 overflow-hidden min-h-0">
<h2 class="text-base text-black shrink-0 leading-none">字体选择</h2>
<div v-overflow-aware class="scrollbar-hover flex-1 overflow-y-auto overflow-x-hidden pr-2"> <div v-overflow-aware class="scrollbar-hover flex-1 overflow-y-auto overflow-x-hidden pr-2">
<FontSelector /> <FontSelector />
</div> </div>

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 32 32">
<rect width="32" height="32" fill="#8552A1" rx="10" transform="matrix(-1 0 0 1 32 0)"/>
<path fill="#FEFDFE" d="m24.845 22.204-5.101-5.105-2.64 2.64 5.105 5.104a1.085 1.085 0 0 0 1.527 0l1.108-1.108a1.09 1.09 0 0 0 0-1.531ZM17.22 18.6l1.382-1.382-1.576-1.576a5.512 5.512 0 0 0-.63-7.032 5.51 5.51 0 0 0-7.785 0c-2.15 2.146-2.146 5.635 0 7.785a5.512 5.512 0 0 0 7.033.63l1.575 1.576Zm-7.541-3.288a3.983 3.983 0 0 1 0-5.635 3.983 3.983 0 0 1 5.635 0 3.983 3.983 0 0 1 0 5.635 3.983 3.983 0 0 1-5.635 0Z"/>
</svg>

After

Width:  |  Height:  |  Size: 611 B

View File

@@ -1,18 +1,72 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed, ref } from 'vue'
import { useFontStore } from '../stores/fontStore' import { useFontStore } from '../stores/fontStore'
import FontTree from './FontTree.vue' import FontTree from './FontTree.vue'
import searchIcon from '../assets/icons/search.svg'
const fontStore = useFontStore() const fontStore = useFontStore()
const searchKeyword = ref('')
const fontTree = computed(() => fontStore.fontTree) const fontTree = computed(() => fontStore.fontTree)
const normalizedSearchKeyword = computed(() => searchKeyword.value.trim().toLowerCase())
const hasSearchKeyword = computed(() => normalizedSearchKeyword.value.length > 0)
const hasMatchedFonts = computed(() => {
if (!hasSearchKeyword.value) {
return fontTree.value.length > 0
}
const keyword = normalizedSearchKeyword.value
return fontTree.value.some((node) => {
if (node.type !== 'category') {
return false
}
if (node.name.toLowerCase().includes(keyword)) {
return true
}
return (node.children ?? []).some(child => {
return child.type === 'font' && child.name.toLowerCase().includes(keyword)
})
})
})
</script> </script>
<template> <template>
<div class="space-y-2"> <div class="space-y-2 pb-1">
<div v-if="fontTree.length === 0" class="text-sm text-gray-500 text-center py-8"> <div class="sticky top-0 z-10 bg-white pt-1 pb-1">
暂无字体 <div class="flex items-center gap-3">
<div class="text-[16px] leading-none text-black font-bold shrink-0">
选择预览字体
</div>
<div class="flex-1 min-w-0">
<div class="h-8 rounded-[10px] bg-[#F3EDF7] pl-2 flex items-center">
<input
v-model="searchKeyword"
type="text"
placeholder="输入搜索字体名称"
aria-label="字体搜索"
class="flex-1 min-w-0 bg-transparent border-none outline-none text-[14px] text-black placeholder-[#a2a0a9]"
/>
<button
type="button"
class="w-8 h-8 shrink-0 p-0 border-0 bg-transparent flex items-center justify-center"
aria-hidden="true"
>
<img :src="searchIcon" alt="" class="w-[24px] h-[24px]" />
</button>
</div>
</div>
</div>
</div> </div>
<FontTree v-else :nodes="fontTree" />
<div v-if="!hasMatchedFonts" class="text-sm text-gray-500 text-center py-8">
{{ hasSearchKeyword ? '未找到匹配字体' : '暂无字体' }}
</div>
<FontTree
v-else
:nodes="fontTree"
:search-keyword="normalizedSearchKeyword"
/>
</div> </div>
</template> </template>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'
import selectAllIcon from '../assets/icons/selectall.svg' import selectAllIcon from '../assets/icons/selectall.svg'
import unselectAllIcon from '../assets/icons/unselectall.svg' import unselectAllIcon from '../assets/icons/unselectall.svg'
import type { FontTreeNode } from '../types/font' import type { FontTreeNode } from '../types/font'
@@ -6,11 +7,47 @@ import { useFontStore } from '../stores/fontStore'
const props = defineProps<{ const props = defineProps<{
nodes: FontTreeNode[] nodes: FontTreeNode[]
searchKeyword?: string
}>() }>()
const fontStore = useFontStore() const fontStore = useFontStore()
const normalizedSearchKeyword = computed(() => (props.searchKeyword ?? '').trim().toLowerCase())
const isSearchMode = computed(() => normalizedSearchKeyword.value.length > 0)
type FontLeafNode = FontTreeNode & { fontInfo: NonNullable<FontTreeNode['fontInfo']> }
function getVisibleChildren(node: FontTreeNode): FontLeafNode[] {
if (node.type !== 'category' || !node.children) {
return []
}
const fontChildren = node.children.filter(
(child): child is FontLeafNode => child.type === 'font' && !!child.fontInfo,
)
if (!isSearchMode.value) {
return fontChildren
}
const keyword = normalizedSearchKeyword.value
if (node.name.toLowerCase().includes(keyword)) {
return fontChildren
}
return fontChildren.filter(child => child.name.toLowerCase().includes(keyword))
}
function shouldRenderCategory(node: FontTreeNode): boolean {
return node.type === 'category' && getVisibleChildren(node).length > 0
}
function isCategoryExpanded(node: FontTreeNode): boolean {
return isSearchMode.value ? true : !!node.expanded
}
function toggleExpand(node: FontTreeNode) { function toggleExpand(node: FontTreeNode) {
if (isSearchMode.value) {
return
}
const next = !node.expanded const next = !node.expanded
node.expanded = next node.expanded = next
fontStore.setCategoryExpanded(node.name, next) fontStore.setCategoryExpanded(node.name, next)
@@ -39,17 +76,11 @@ function isInPreview(node: FontTreeNode): boolean {
} }
function getCategoryFontIds(node: FontTreeNode): string[] { function getCategoryFontIds(node: FontTreeNode): string[] {
if (node.type !== 'category' || !node.children) { return getVisibleChildren(node).map(child => child.fontInfo.id)
return []
}
return node.children
.filter((child): child is FontTreeNode & { fontInfo: NonNullable<FontTreeNode['fontInfo']> } => child.type === 'font' && !!child.fontInfo)
.map(child => child.fontInfo.id)
} }
function getCategoryFontCount(node: FontTreeNode): number { function getCategoryFontCount(node: FontTreeNode): number {
return getCategoryFontIds(node).length return getVisibleChildren(node).length
} }
function isCategoryAllInPreview(node: FontTreeNode): boolean { function isCategoryAllInPreview(node: FontTreeNode): boolean {
@@ -77,19 +108,21 @@ function handleCategorySelectAll(node: FontTreeNode, event: Event) {
<div class="space-y-0"> <div class="space-y-0">
<div v-for="node in nodes" :key="node.name"> <div v-for="node in nodes" :key="node.name">
<!-- 分类节点 --> <!-- 分类节点 -->
<div v-if="node.type === 'category'" class="relative mb-3"> <div v-if="shouldRenderCategory(node)" class="relative mb-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<!-- 左侧展开图标 --> <!-- 左侧展开图标 -->
<div class="tree-icon-wrapper"> <div class="tree-icon-wrapper">
<button <button
@click="toggleExpand(node)" @click="toggleExpand(node)"
class="tree-toggle" class="tree-toggle"
:disabled="isSearchMode"
> >
<img <img
v-if="node.expanded" v-if="isCategoryExpanded(node)"
src="../assets/icons/zhedie.svg" src="../assets/icons/zhedie.svg"
alt="收起" alt="收起"
class="w-[15px] h-[15px]" class="w-[15px] h-[15px]"
:class="{ 'opacity-70': isSearchMode }"
/> />
<img <img
v-else v-else
@@ -103,7 +136,8 @@ function handleCategorySelectAll(node: FontTreeNode, event: Event) {
<!-- 分类标题 --> <!-- 分类标题 -->
<div <div
@click="toggleExpand(node)" @click="toggleExpand(node)"
class="text-base font-medium text-black cursor-pointer flex-1 ml-2" class="text-base font-medium text-black flex-1 ml-2"
:class="isSearchMode ? 'cursor-default' : 'cursor-pointer'"
> >
{{ node.name }}{{ getCategoryFontCount(node) }}字体 {{ node.name }}{{ getCategoryFontCount(node) }}字体
</div> </div>
@@ -125,12 +159,12 @@ function handleCategorySelectAll(node: FontTreeNode, event: Event) {
</div> </div>
<!-- 竖直连接线 --> <!-- 竖直连接线 -->
<div v-if="node.expanded && node.children" class="tree-vertical-line"></div> <div v-if="isCategoryExpanded(node) && getVisibleChildren(node).length > 0" class="tree-vertical-line"></div>
<!-- 字体列表 --> <!-- 字体列表 -->
<div v-if="node.expanded && node.children" class="flex flex-col gap-3 mt-3"> <div v-if="isCategoryExpanded(node) && getVisibleChildren(node).length > 0" class="flex flex-col gap-3 mt-3">
<div <div
v-for="child in node.children" v-for="child in getVisibleChildren(node)"
:key="child.name" :key="child.name"
class="flex items-center gap-2 border-b border-[#c9cdd4] pb-2 relative" class="flex items-center gap-2 border-b border-[#c9cdd4] pb-2 relative"
> >