362 lines
7.3 KiB
Vue
362 lines
7.3 KiB
Vue
<template>
|
||
<view class="category-tree-container">
|
||
<!-- 搜索栏 -->
|
||
<view class="search-section" v-if="showSearch">
|
||
<view class="search-input" @click="handleSearchClick">
|
||
<text class="search-icon">🔍</text>
|
||
<text class="search-placeholder">搜索体系分类...</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 分类树 -->
|
||
<scroll-view
|
||
class="category-scroll"
|
||
scroll-y
|
||
:scroll-with-animation="true"
|
||
:show-scrollbar="false"
|
||
>
|
||
<!-- 空状态 -->
|
||
<empty-state
|
||
v-if="!loading && categories.length === 0"
|
||
:title="'暂无分类'"
|
||
:description="'暂无体系分类数据'"
|
||
:show-action="true"
|
||
:action-text="'刷新'"
|
||
@action="$emit('refresh')"
|
||
/>
|
||
|
||
<!-- 分类列表 -->
|
||
<view v-else class="category-list">
|
||
<view
|
||
v-for="category in categories"
|
||
:key="category.id"
|
||
class="category-item"
|
||
:class="{
|
||
'category-level-1': true,
|
||
'expanded': category.expanded,
|
||
'selected': selectedCategory?.id === category.id
|
||
}"
|
||
>
|
||
<!-- 一级分类 -->
|
||
<view
|
||
class="category-header"
|
||
@click="handleCategoryClick(category)"
|
||
>
|
||
<view class="category-info">
|
||
<text class="category-icon">📚</text>
|
||
<text class="category-name">{{ category.name }}</text>
|
||
<text class="category-count" v-if="category.children.length > 0">
|
||
({{ category.children.length }})
|
||
</text>
|
||
</view>
|
||
|
||
<view class="category-actions">
|
||
<!-- 展开/收起箭头 -->
|
||
<text
|
||
class="expand-icon"
|
||
v-if="category.children.length > 0"
|
||
@click.stop="toggleCategoryExpand(category)"
|
||
>
|
||
{{ category.expanded ? '▼' : '▶' }}
|
||
</text>
|
||
|
||
<!-- 选中标记 -->
|
||
<text
|
||
class="selected-icon"
|
||
v-if="selectedCategory?.id === category.id"
|
||
>
|
||
✓
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 二级分类(子分类) -->
|
||
<view
|
||
class="subcategory-list"
|
||
v-if="category.expanded && category.children.length > 0"
|
||
>
|
||
<view
|
||
v-for="child in category.children"
|
||
:key="child.id"
|
||
class="subcategory-item"
|
||
:class="{
|
||
'category-level-2': true,
|
||
'selected': selectedCategory?.id === child.id
|
||
}"
|
||
@click="handleSubcategoryClick(child, category)"
|
||
>
|
||
<view class="subcategory-info">
|
||
<text class="subcategory-icon">📖</text>
|
||
<text class="subcategory-name">{{ child.name }}</text>
|
||
</view>
|
||
|
||
<text
|
||
class="selected-icon"
|
||
v-if="selectedCategory?.id === child.id"
|
||
>
|
||
✓
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import EmptyState from '@/components/common/empty-state.vue'
|
||
|
||
export default {
|
||
name: 'CategoryTree',
|
||
components: {
|
||
EmptyState
|
||
},
|
||
props: {
|
||
// 分类数据
|
||
categories: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
// 选中的分类
|
||
selectedCategory: {
|
||
type: Object,
|
||
default: null
|
||
},
|
||
// 是否显示搜索
|
||
showSearch: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
// 加载状态
|
||
loading: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
// 本地状态
|
||
}
|
||
},
|
||
methods: {
|
||
// 处理分类点击
|
||
handleCategoryClick(category) {
|
||
this.$emit('category-click', category)
|
||
},
|
||
|
||
// 处理子分类点击
|
||
handleSubcategoryClick(childCategory, parentCategory) {
|
||
this.$emit('subcategory-click', {
|
||
...childCategory,
|
||
parentCategory: {
|
||
id: parentCategory.id,
|
||
name: parentCategory.name
|
||
}
|
||
})
|
||
},
|
||
|
||
// 切换分类展开状态
|
||
toggleCategoryExpand(category) {
|
||
this.$emit('toggle-expand', {
|
||
...category,
|
||
expanded: !category.expanded
|
||
})
|
||
},
|
||
|
||
// 处理搜索点击
|
||
handleSearchClick() {
|
||
this.$emit('search-click')
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.category-tree-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
background-color: #f8f8f8;
|
||
}
|
||
|
||
/* 搜索栏 */
|
||
.search-section {
|
||
padding: 20rpx 30rpx;
|
||
background-color: #fff;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
}
|
||
|
||
.search-input {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 20rpx 30rpx;
|
||
background-color: #f8f8f8;
|
||
border-radius: 50rpx;
|
||
border: 1rpx solid #e5e5e5;
|
||
}
|
||
|
||
.search-icon {
|
||
font-size: 32rpx;
|
||
margin-right: 16rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.search-placeholder {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
flex: 1;
|
||
}
|
||
|
||
/* 分类滚动区域 */
|
||
.category-scroll {
|
||
flex: 1;
|
||
}
|
||
|
||
/* 分类列表 */
|
||
.category-list {
|
||
padding: 20rpx 0;
|
||
}
|
||
|
||
/* 一级分类 */
|
||
.category-item {
|
||
margin-bottom: 10rpx;
|
||
background-color: #fff;
|
||
border-radius: 16rpx;
|
||
overflow: hidden;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.category-item.selected {
|
||
background-color: #f0f9ff;
|
||
border-left: 6rpx solid #3cc51f;
|
||
}
|
||
|
||
.category-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 30rpx;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.category-header:active {
|
||
background-color: #f8f8f8;
|
||
}
|
||
|
||
.category-info {
|
||
display: flex;
|
||
align-items: center;
|
||
flex: 1;
|
||
}
|
||
|
||
.category-icon {
|
||
font-size: 36rpx;
|
||
margin-right: 20rpx;
|
||
color: #3cc51f;
|
||
}
|
||
|
||
.category-name {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
flex: 1;
|
||
}
|
||
|
||
.category-count {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-left: 12rpx;
|
||
}
|
||
|
||
.category-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.expand-icon {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
padding: 10rpx;
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.expand-icon:active {
|
||
color: #3cc51f;
|
||
}
|
||
|
||
.selected-icon {
|
||
font-size: 28rpx;
|
||
color: #3cc51f;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 二级分类列表 */
|
||
.subcategory-list {
|
||
background-color: #fafafa;
|
||
border-top: 1rpx solid #f0f0f0;
|
||
padding: 0 30rpx;
|
||
}
|
||
|
||
.subcategory-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 25rpx 0;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.subcategory-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.subcategory-item:active {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.subcategory-item.selected {
|
||
background-color: #f0f9ff;
|
||
}
|
||
|
||
.subcategory-info {
|
||
display: flex;
|
||
align-items: center;
|
||
flex: 1;
|
||
}
|
||
|
||
.subcategory-icon {
|
||
font-size: 28rpx;
|
||
margin-right: 20rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.subcategory-name {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
flex: 1;
|
||
}
|
||
|
||
/* 动画效果 */
|
||
.category-item.expanded .expand-icon {
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
/* 悬停效果(在支持hover的设备上) */
|
||
@media (hover: hover) {
|
||
.category-header:hover {
|
||
background-color: #f8f8f8;
|
||
}
|
||
|
||
.subcategory-item:hover {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.expand-icon:hover {
|
||
color: #3cc51f;
|
||
}
|
||
}
|
||
</style> |