update at 2026-02-07 15:41:30
This commit is contained in:
@@ -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;">
|
||||
<!-- 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">
|
||||
<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">
|
||||
<FontSelector />
|
||||
</div>
|
||||
|
||||
4
frontend/src/assets/icons/search.svg
Normal file
4
frontend/src/assets/icons/search.svg
Normal 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 |
@@ -1,18 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useFontStore } from '../stores/fontStore'
|
||||
import FontTree from './FontTree.vue'
|
||||
import searchIcon from '../assets/icons/search.svg'
|
||||
|
||||
const fontStore = useFontStore()
|
||||
const searchKeyword = ref('')
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="space-y-2">
|
||||
<div v-if="fontTree.length === 0" class="text-sm text-gray-500 text-center py-8">
|
||||
暂无字体
|
||||
<div class="space-y-2 pb-1">
|
||||
<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>
|
||||
<FontTree v-else :nodes="fontTree" />
|
||||
<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 v-if="!hasMatchedFonts" class="text-sm text-gray-500 text-center py-8">
|
||||
{{ hasSearchKeyword ? '未找到匹配字体' : '暂无字体' }}
|
||||
</div>
|
||||
<FontTree
|
||||
v-else
|
||||
:nodes="fontTree"
|
||||
:search-keyword="normalizedSearchKeyword"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import selectAllIcon from '../assets/icons/selectall.svg'
|
||||
import unselectAllIcon from '../assets/icons/unselectall.svg'
|
||||
import type { FontTreeNode } from '../types/font'
|
||||
@@ -6,11 +7,47 @@ import { useFontStore } from '../stores/fontStore'
|
||||
|
||||
const props = defineProps<{
|
||||
nodes: FontTreeNode[]
|
||||
searchKeyword?: string
|
||||
}>()
|
||||
|
||||
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) {
|
||||
if (isSearchMode.value) {
|
||||
return
|
||||
}
|
||||
const next = !node.expanded
|
||||
node.expanded = next
|
||||
fontStore.setCategoryExpanded(node.name, next)
|
||||
@@ -39,17 +76,11 @@ function isInPreview(node: FontTreeNode): boolean {
|
||||
}
|
||||
|
||||
function getCategoryFontIds(node: FontTreeNode): string[] {
|
||||
if (node.type !== 'category' || !node.children) {
|
||||
return []
|
||||
}
|
||||
|
||||
return node.children
|
||||
.filter((child): child is FontTreeNode & { fontInfo: NonNullable<FontTreeNode['fontInfo']> } => child.type === 'font' && !!child.fontInfo)
|
||||
.map(child => child.fontInfo.id)
|
||||
return getVisibleChildren(node).map(child => child.fontInfo.id)
|
||||
}
|
||||
|
||||
function getCategoryFontCount(node: FontTreeNode): number {
|
||||
return getCategoryFontIds(node).length
|
||||
return getVisibleChildren(node).length
|
||||
}
|
||||
|
||||
function isCategoryAllInPreview(node: FontTreeNode): boolean {
|
||||
@@ -77,19 +108,21 @@ function handleCategorySelectAll(node: FontTreeNode, event: Event) {
|
||||
<div class="space-y-0">
|
||||
<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="tree-icon-wrapper">
|
||||
<button
|
||||
@click="toggleExpand(node)"
|
||||
class="tree-toggle"
|
||||
:disabled="isSearchMode"
|
||||
>
|
||||
<img
|
||||
v-if="node.expanded"
|
||||
v-if="isCategoryExpanded(node)"
|
||||
src="../assets/icons/zhedie.svg"
|
||||
alt="收起"
|
||||
class="w-[15px] h-[15px]"
|
||||
:class="{ 'opacity-70': isSearchMode }"
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
@@ -103,7 +136,8 @@ function handleCategorySelectAll(node: FontTreeNode, event: Event) {
|
||||
<!-- 分类标题 -->
|
||||
<div
|
||||
@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) }}字体)
|
||||
</div>
|
||||
@@ -125,12 +159,12 @@ function handleCategorySelectAll(node: FontTreeNode, event: Event) {
|
||||
</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
|
||||
v-for="child in node.children"
|
||||
v-for="child in getVisibleChildren(node)"
|
||||
:key="child.name"
|
||||
class="flex items-center gap-2 border-b border-[#c9cdd4] pb-2 relative"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user