update at 2026-02-07 11:14:09
This commit is contained in:
172
frontend/src/components/FontTree.vue
Normal file
172
frontend/src/components/FontTree.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<script setup lang="ts">
|
||||
import type { FontTreeNode } from '../types/font'
|
||||
import { useFontStore } from '../stores/fontStore'
|
||||
|
||||
const props = defineProps<{
|
||||
nodes: FontTreeNode[]
|
||||
}>()
|
||||
|
||||
const fontStore = useFontStore()
|
||||
|
||||
function toggleExpand(node: FontTreeNode) {
|
||||
const next = !node.expanded
|
||||
node.expanded = next
|
||||
fontStore.setCategoryExpanded(node.name, next)
|
||||
}
|
||||
|
||||
function handlePreviewClick(node: FontTreeNode, event: Event) {
|
||||
event.stopPropagation()
|
||||
if (node.type === 'font' && node.fontInfo) {
|
||||
fontStore.togglePreview(node.fontInfo.id)
|
||||
}
|
||||
}
|
||||
|
||||
function handleFavoriteClick(node: FontTreeNode, event: Event) {
|
||||
event.stopPropagation()
|
||||
if (node.type === 'font' && node.fontInfo) {
|
||||
fontStore.toggleFavorite(node.fontInfo.id)
|
||||
}
|
||||
}
|
||||
|
||||
function isFavorite(node: FontTreeNode): boolean {
|
||||
return node.type === 'font' && node.fontInfo ? fontStore.favoriteFontIds.has(node.fontInfo.id) : false
|
||||
}
|
||||
|
||||
function isInPreview(node: FontTreeNode): boolean {
|
||||
return node.type === 'font' && node.fontInfo ? fontStore.previewFontIds.has(node.fontInfo.id) : false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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 class="flex items-center">
|
||||
<!-- 左侧展开图标 -->
|
||||
<div class="tree-icon-wrapper">
|
||||
<button
|
||||
@click="toggleExpand(node)"
|
||||
class="tree-toggle"
|
||||
>
|
||||
<img
|
||||
v-if="node.expanded"
|
||||
src="/assets/icons/zhedie.svg"
|
||||
alt="收起"
|
||||
class="w-[15px] h-[15px]"
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
src="/assets/icons/icons_idx%20_12.svg"
|
||||
alt="展开"
|
||||
class="w-[15px] h-[15px]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 分类标题 -->
|
||||
<div
|
||||
@click="toggleExpand(node)"
|
||||
class="text-base font-medium text-black cursor-pointer flex-1 ml-2"
|
||||
>
|
||||
{{ node.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 竖直连接线 -->
|
||||
<div v-if="node.expanded && node.children" class="tree-vertical-line"></div>
|
||||
|
||||
<!-- 字体列表 -->
|
||||
<div v-if="node.expanded && node.children" class="flex flex-col gap-3 mt-3">
|
||||
<div
|
||||
v-for="(child, index) in node.children"
|
||||
:key="child.name"
|
||||
class="flex items-center gap-2 border-b border-[#c9cdd4] pb-2 relative"
|
||||
>
|
||||
<!-- 水平连接线 -->
|
||||
<div class="tree-horizontal-line"></div>
|
||||
|
||||
<!-- 字体图标 -->
|
||||
<div class="w-4 h-4 shrink-0 ml-[17px]">
|
||||
<img src="/assets/icons/icons_idx%20_18.svg" alt="font" class="w-full h-full" />
|
||||
</div>
|
||||
|
||||
<!-- 字体名称 -->
|
||||
<div class="flex-1 text-xs text-[#86909c]">
|
||||
{{ child.name }}
|
||||
</div>
|
||||
|
||||
<!-- 预览复选框 -->
|
||||
<button
|
||||
@click="handlePreviewClick(child, $event)"
|
||||
class="w-[18px] h-[18px] shrink-0 border rounded-full flex items-center justify-center p-0 bg-transparent"
|
||||
:class="isInPreview(child) ? 'bg-[#9b6bc2] border-[#9b6bc2]' : 'border-[#c9cdd4]'"
|
||||
>
|
||||
<img v-if="isInPreview(child)" src="/assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
||||
</button>
|
||||
|
||||
<!-- 收藏按钮 -->
|
||||
<button
|
||||
@click="handleFavoriteClick(child, $event)"
|
||||
class="w-[18px] h-[17px] shrink-0 p-0 border-0 bg-transparent"
|
||||
>
|
||||
<img
|
||||
src="/assets/icons/icons_idx%20_19.svg"
|
||||
alt="收藏"
|
||||
class="w-full h-full"
|
||||
:class="isFavorite(child) ? 'favorite-active' : ''"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tree-icon-wrapper {
|
||||
position: relative;
|
||||
width: 17px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tree-toggle {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tree-vertical-line {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 20px;
|
||||
bottom: 12px;
|
||||
width: 1px;
|
||||
background: #c9cdd4;
|
||||
}
|
||||
|
||||
.tree-horizontal-line {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 12px;
|
||||
width: 10px;
|
||||
height: 1px;
|
||||
background: #c9cdd4;
|
||||
}
|
||||
|
||||
.favorite-active {
|
||||
filter: brightness(0) saturate(100%) invert(16%) sepia(96%) saturate(7491%) hue-rotate(356deg) brightness(99%) contrast(119%);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user